Disp BBS - KnucklesNote 板
那克斯的學習筆記
http://disp.cc/b/KnucklesNote
2024-02-21T21:40:01Z
Knuckles
[Linode] Object Storage 使用 AWS SDK for PHP
http://disp.cc/b/11-gLmU
2024-01-09T04:11:46+08:00
2024-01-09T04:11:46+08:00
參考前一篇設定 Linode 的 Object Storage
[Linode] Object Storage 相容 S3 的檔案儲存空間 - KnucklesNote板 - Disp BBS
這篇記錄如何使用 PHP 存取 Object Storage 上的檔案
因為與 Amazon S3 相容,所以可以用 AWS SDK for PHP
https://www.linode.com/docs/products/storage/object-storage/guides/aws-sdk-for-php/
要使用 AWS SDK,PHP 至少要有 5.5,如果自己下載 AWS SDK 的話,相依的套件會需要 PHP 7.2 才能執行,所以 PHP 5.6 的話要用 Composer 安裝才能解決相依問題, PHP 5.6 會安裝的版本為 aws/aws-sdk-php (3.278.3)
在專案目錄執行 composer 安裝 aws/aws-sdk-php
$ composer require aws/aws-sdk-php
裝好 AWS SDK 後,新增一個 test.php ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Linode] Object Storage 使用 AWS SDK for PHP<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2024-01-09 Tue. 04:11:45</div><hr color="#008080" />
<div class="img" data-ori_w="1200" data-ori_h="600" style="width:600px;height:300px"><img style="max-width:100%;" data-ratio="2" src="https://img.disp.cc/u/o/1/YzU2YWI1YTY.png" alt="[圖]" /></div>
參考前一篇設定 Linode 的 Object Storage
<a href="https://disp.cc/b/KnucklesNote/gLmL" target="_blank" rel="nofollow">[Linode] Object Storage 相容 S3 的檔案儲存空間 - KnucklesNote板 - Disp BBS</a>
這篇記錄如何使用 PHP 存取 Object Storage 上的檔案
因為與 Amazon S3 相容,所以可以用 AWS SDK for PHP
<a href="https://www.linode.com/docs/products/storage/object-storage/guides/aws-sdk-for-php/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/storage/object-storage/guides/aws-sdk-for-php/</a>
要使用 AWS SDK,PHP 至少要有 5.5,如果自己下載 AWS SDK 的話,相依的套件會需要 PHP 7.2 才能執行,所以 PHP 5.6 的話要用 Composer 安裝才能解決相依問題, PHP 5.6 會安裝的版本為 aws/aws-sdk-php (3.278.3)
在專案目錄執行 composer 安裝 aws/aws-sdk-php
<code class="code-inline">$ composer require aws/aws-sdk-php</code>
裝好 AWS SDK 後,新增一個 test.php 檔
<div class="highlight"><span></span><span class="cp"><?php</span>
<span class="k">require</span> <span class="s1">'vendor/autoload.php'</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Aws\S3\S3Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Aws\Exception\AwsException</span><span class="p">;</span>
<span class="nv">$config</span> <span class="o">=</span>
<span class="p">[</span>
<span class="s1">'version'</span> <span class="o">=></span> <span class="s1">'latest'</span><span class="p">,</span>
<span class="s1">'region'</span> <span class="o">=></span> <span class="s1">'jp-osa-1'</span><span class="p">,</span>
<span class="s1">'endpoint'</span> <span class="o">=></span> <span class="s1">'<a href="https://jp-osa-1.linodeobjects.com/'" target="_blank" rel="nofollow">https://jp-osa-1.linodeobjects.com/'</a></span><span class="p">,</span>
<span class="s1">'credentials'</span> <span class="o">=></span>
<span class="p">[</span>
<span class="s1">'key'</span> <span class="o">=></span> <span class="s1">'[access-key]'</span><span class="p">,</span>
<span class="s1">'secret'</span> <span class="o">=></span> <span class="s1">'[secret-key]'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">];</span>
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">\Aws\S3\S3Client</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</div>
其中 region 和 endpoint 依照 <a href="https://www.linode.com/docs/products/storage/object-storage/guides/urls/#cluster-url-s3-endpoint" target="_blank" rel="nofollow">cluster-url-s3-endpoint</a>,選擇使用的 Data Center,
'region' 輸入 Cluster ID, 'endpoint' 輸入 Cluster URL
[access-key] 和 [secret-key] 換成在 Linode 控制台產生的 Access Key 和 Secret
<div class="img" data-ori_w="701" data-ori_h="153" style="width:351px;height:77px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/f7qdmJizvdZ_5ymRFgLWQkwAvLOZGOQSC43AMh33B7XZkA_2bxTkJ3A0rMCRT1frUzeFsyeJwXC6HHhb71sT5x5RMeHpdPV755BaUKKVh1pOBkm3TWsJ6ZlM9vyPbZfxp37tx8HPnSOEdUY1jJ1xXaY" alt="[圖]" /></div>
接著就能用物件 $client 存取 Bucket 中的檔案
<div class="highlight"><span></span><span class="c1">// 列出 $bucket 裡面,開頭為 'test/' 的檔案</span>
<span class="nv">$bucket</span> <span class="o">=</span> <span class="s1">'disp'</span><span class="p">;</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-></span><span class="na">listObjects</span><span class="p">([</span><span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Prefix'</span> <span class="o">=></span> <span class="s1">'test/'</span><span class="p">]);</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$result</span><span class="p">[</span><span class="s1">'Contents'</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$object</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="nv">$object</span><span class="p">[</span><span class="s1">'Key'</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 上傳檔案 /path/to/logo.jpg 到 $bucket 的 test/logo.jpg</span>
<span class="nv">$path</span> <span class="o">=</span> <span class="s1">'/path/to/logo.jpg'</span><span class="p">;</span>
<span class="nv">$key</span> <span class="o">=</span> <span class="s1">'test/logo.jpg'</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-></span><span class="na">putObject</span><span class="p">([</span><span class="s1">'Bucket'</span><span class="o">=></span><span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Key'</span><span class="o">=></span><span class="nv">$key</span><span class="p">,</span> <span class="s1">'SourceFile'</span><span class="o">=></span><span class="nv">$path</span><span class="p">,</span> <span class="s1">'ACL'</span> <span class="o">=></span> <span class="s1">'public-read'</span><span class="p">]);</span>
<span class="c1">// 檢查檔案 $key 是否存在,回傳值為 Bool</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-></span><span class="na">doesObjectExist</span><span class="p">(</span><span class="nv">$bucket</span><span class="p">,</span> <span class="nv">$key</span><span class="p">);</span>
<span class="c1">// 下載 $bucket 的檔案 $key 存到 $path</span>
<span class="nv">$client</span><span class="o">-></span><span class="na">getObject</span><span class="p">([</span><span class="s1">'Bucket'</span><span class="o">=></span><span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Key'</span><span class="o">=></span><span class="nv">$key</span><span class="p">,</span> <span class="s1">'SaveAs'</span><span class="o">=></span><span class="nv">$path</span><span class="p">);</span>
<span class="c1">// 刪除 $bucket 的檔案 $key</span>
<span class="nv">$client</span><span class="o">-></span><span class="na">deleteObject</span><span class="p">([</span><span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Key'</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">]);</span>
</div>
可以將其他要記錄的資訊寫在 Metadata
<div class="highlight"><span></span><span class="c1">// 例如要再記錄圖檔的寬高</span>
<span class="k">list</span><span class="p">(</span><span class="nv">$width</span><span class="p">,</span> <span class="nv">$height</span><span class="p">)</span> <span class="o">=</span> <span class="nb">getimagesize</span><span class="p">(</span><span class="nv">$path</span><span class="p">);</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-></span><span class="na">putObject</span><span class="p">([</span>
<span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Key'</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">,</span> <span class="s1">'SourceFile'</span> <span class="o">=></span> <span class="nv">$path</span><span class="p">,</span> <span class="s1">'ACL'</span> <span class="o">=></span> <span class="s1">'public-read'</span><span class="p">,</span>
<span class="s1">'Metadata'</span> <span class="o">=></span> <span class="p">[</span> <span class="s1">'width'</span> <span class="o">=></span> <span class="nv">$width</span><span class="p">,</span> <span class="s1">'height'</span> <span class="o">=></span> <span class="nv">$height</span> <span class="p">],</span>
<span class="p">]);</span>
<span class="c1">// 只讀取檔案的資訊,不下載檔案</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-></span><span class="na">getObject</span><span class="p">([</span><span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$bucket</span><span class="p">,</span> <span class="s1">'Key'</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">]);</span>
<span class="nv">$metadata</span> <span class="o">=</span> <span class="nv">$result</span><span class="p">[</span><span class="s1">'Metadata'</span><span class="p">];</span>
<span class="k">echo</span> <span class="s2">"size: "</span><span class="o">.</span><span class="nv">$result</span><span class="p">[</span><span class="s1">'ContentLength'</span><span class="p">]</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// 等於 filesize($path)</span>
<span class="nv">$etag</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'"'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$result</span><span class="p">[</span><span class="s1">'ETag'</span><span class="p">]);</span> <span class="c1">// 要去掉多餘的雙引號</span>
<span class="k">echo</span> <span class="s2">"ETag: "</span><span class="o">.</span><span class="nv">$etag</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// 一般等於 md5_file($path),可用來檢查檔案是否相同</span>
<span class="k">echo</span> <span class="s2">"filetype: "</span><span class="o">.</span><span class="nv">$result</span><span class="p">[</span><span class="s1">'ContentType'</span><span class="p">]</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// image/jpeg</span>
<span class="k">echo</span> <span class="s2">"width: "</span><span class="o">.</span><span class="nv">$metadata</span><span class="p">[</span><span class="s1">'width'</span><span class="p">]</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// 圖檔的寬</span>
<span class="k">echo</span> <span class="s2">"height: "</span><span class="o">.</span><span class="nv">$metadata</span><span class="p">[</span><span class="s1">'height'</span><span class="p">]</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// 圖檔的高</span>
<span class="nv">$date</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateTime</span><span class="p">(</span><span class="nv">$result</span><span class="p">[</span><span class="s1">'LastModified'</span><span class="p">]);</span> <span class="c1">// 預設是UTC時間</span>
<span class="nv">$date</span><span class="o">-></span><span class="na">setTimezone</span><span class="p">(</span><span class="k">new</span> <span class="nx">DateTimeZone</span><span class="p">(</span><span class="s1">'Asia/Taipei'</span><span class="p">));</span>
<span class="k">echo</span> <span class="s2">"LastModified: "</span><span class="o">.</span><span class="nv">$date</span><span class="o">-></span><span class="na">format</span><span class="p">(</span><span class="s1">'Y-m-d H:i:s'</span><span class="p">)</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> <span class="c1">// 時區為台北的上傳時間</span>
</div>
若上傳時想加上 md5 檢查檔案上傳的完整性,可加上 ContentMD5,參考 <a href="https://docs.aws.amazon.com/zh_tw/AmazonS3/latest/userguide/checking-object-integrity.html#checking-object-integrity-md5" target="_blank" rel="nofollow">https://docs.aws.amazon.com/zh_tw/Am...#checking-object-integrity-md5</a>
<div class="highlight"><span></span><span class="c1">// 要使用 binary format 的 md5 再加上 base64_encode</span>
<span class="nv">$md5base64</span> <span class="o">=</span> <span class="nb">base64_encode</span><span class="p">(</span><span class="nb">md5_file</span><span class="p">(</span><span class="nv">$path</span><span class="p">,</span> <span class="k">true</span><span class="p">));</span>
<span class="k">try</span><span class="p">{</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-></span><span class="na">putObject</span><span class="p">([</span>
<span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$bucket</span><span class="p">,</span>
<span class="s1">'Key'</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">,</span>
<span class="s1">'SourceFile'</span> <span class="o">=></span> <span class="nv">$path</span><span class="p">,</span>
<span class="s1">'ACL'</span> <span class="o">=></span> <span class="s1">'public-read'</span><span class="p">,</span>
<span class="s1">'ContentMD5'</span> <span class="o">=></span> <span class="nv">$md5base64</span><span class="p">,</span>
<span class="p">]);</span>
<span class="k">echo</span> <span class="s2">"上傳成功</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span><span class="k">catch</span><span class="p">(</span><span class="nx">Exception</span> <span class="nv">$e</span><span class="p">){</span>
<span class="k">echo</span> <span class="s2">"上傳失敗"</span><span class="o">.</span><span class="nv">$e</span><span class="o">-></span><span class="na">getMessage</span><span class="p">()</span><span class="o">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
</div>
若驗證失敗會出現錯誤訊息 "400 Bad Request"
若檔案超過16MB被分割上傳的話不能用這個方法檢查
若上傳 s3 後想要刪掉主機上的檔案,可能會被上傳程序鎖定而無法馬上刪除,可以先將檔案內容讀到變數,然後上傳路徑 SourceFile 改為用內容 Body
<div class="highlight"><span></span><span class="nv">$file</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="nv">$path</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">);</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$s3client</span><span class="o">-></span><span class="na">putObject</span><span class="p">([</span>
<span class="s1">'Bucket'</span> <span class="o">=></span> <span class="nv">$s3_bucket</span><span class="p">,</span>
<span class="s1">'Key'</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">,</span>
<span class="s1">'Body'</span> <span class="o">=></span> <span class="nv">$file</span><span class="p">,</span> <span class="c1">//可以輸入檔案的 binary 或 resource</span>
<span class="s1">'ACL'</span> <span class="o">=></span> <span class="s1">'public-read'</span><span class="p">,</span>
<span class="s1">'Metadata'</span> <span class="o">=></span> <span class="p">[</span>
<span class="s1">'width'</span> <span class="o">=></span> <span class="nv">$width</span><span class="p">,</span>
<span class="s1">'height'</span> <span class="o">=></span> <span class="nv">$height</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">]);</span>
<span class="k">echo</span> <span class="s2">"上傳成功</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
<span class="nb">unlink</span><span class="p">(</span><span class="nv">$path</span><span class="p">);</span> <span class="c1">// 刪除主機上的檔案</span>
</div>
測試在 Windows 時才會鎖定,在 Linux 不會被鎖定用 SourceFile 即可
其他的功能可參考
<a href="https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html" target="_blank" rel="nofollow">https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2024-01-09 04:11:45 (台灣)</span></div></pre>
Knuckles
[Linode] Object Storage 相容 S3 的檔案儲存空間
http://disp.cc/b/11-gLmL
2024-01-09T03:22:31+08:00
2024-01-09T04:12:49+08:00
Object Storage 是 Linode 提供的檔案儲存空間服務,
與 Amazon S3 (Simple Storage Service) 相容,
可以用來上傳大量靜態檔案讓人連結。
登入 Linode 控制台,點「Create Bucket」,建立一個 Bucket 後,
就會啟用 Object Storage,只要啟動後,每月就要基本費用 $5 美金,
如果要停用的話,要在 Account / Settings 裡點「Cancel Object Storage」
每月 $5 美金就可以有 250G 的空間,超過 250G 的話 $0.02/GB,
傳入的流量不計費,傳出的流量與帳號的總流量限制共用,
啟用 Object Storage 時會在帳號的總流量限制加上 1TB。
# 建立 Bucket
建立一個 Bucket,取一個名稱並選擇區域,此名稱會用做網址所以不能與同區域的其他人重覆,
例如下圖建立的 Bucket 網址為 disp.jp-osa-1.linodeobjects.com
點進建立的 Bucket 後,可在這邊建立資料夾與上傳檔案
點擊檔案會 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Linode] Object Storage 相容 S3 的檔案儲存空間<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2024-01-09 Tue. 03:22:30</div><hr color="#008080" />
Object Storage 是 Linode 提供的檔案儲存空間服務,
與 Amazon S3 (Simple Storage Service) 相容,
可以用來上傳大量靜態檔案讓人連結。
<div class="img" data-ori_w="1166" data-ori_h="850" style="width:777px;height:567px"><img style="max-width:100%;" data-ratio="1.5" src="https://lh7-us.googleusercontent.com/jHN8bfcKXxSim1X-xCQO6UuDvbf1lU9czz3JQ3bYEC6uQ98-eFxjMmbyxZvkoy1iTMHJBrpbesN9HL5FIQ63T9we517ehq3E9yB0X2_ciknDZNaLI22e8_l5PF3UeYXDEOFsCI1aX3G0dP2iWCpxmmQ" alt="[圖]" /></div>
登入 Linode 控制台,點「Create Bucket」,建立一個 Bucket 後,
就會啟用 Object Storage,只要啟動後,每月就要基本費用 $5 美金,
如果要停用的話,要在 Account / Settings 裡點「Cancel Object Storage」
每月 $5 美金就可以有 250G 的空間,超過 250G 的話 $0.02/GB,
傳入的流量不計費,傳出的流量與帳號的總流量限制共用,
啟用 Object Storage 時會在帳號的總流量限制加上 1TB。
# <span style="color:#00F000">建立 Bucket</span>
建立一個 Bucket,取一個名稱並選擇區域,此名稱會用做網址所以不能與同區域的其他人重覆,
例如下圖建立的 Bucket 網址為 disp.jp-osa-1.linodeobjects.com
<div class="img" data-ori_w="609" data-ori_h="526" style="width:435px;height:376px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/LZnga8f5S2Nc0xHKBu60S7S40E9pv65M3dhr1tJAHLq5zydVBTkHk7Fdozdh2IUgEM5WTc61Q9WjiVQckduVaHF3VZXEYaFUNN-dCWwNcVdPOwGOF64dg7dNNKkVxkcrRsBGr1OH_9gzn9QZbSyxjzQ" alt="[圖]" /></div>
<div class="img" data-ori_w="1074" data-ori_h="269" style="width:767px;height:192px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/KKUoAfe6s55S7FwfuruRp71LcsTdTfWLftMnHDmTzswuFOLQ9nmlJgaiblZd38IZ_HfWMSi_OL3t-WLoyAHQdDZXj8gOpNwQA9AODLMCAINkJMvZBVd7apvvs_nQD6RDdIi9QvHM5qKKffYxhqJXja8" alt="[圖]" /></div>
點進建立的 Bucket 後,可在這邊建立資料夾與上傳檔案
<div class="img" data-ori_w="1087" data-ori_h="603" style="width:776px;height:431px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/Tx0s99tnMhMDoBsOT2wDJcSYf6iMPVgrJjODuCgf7gHw1_Y6vv4xBAmIZJsQLNo8fLRNtLIDWz4_4FcM7GKfy2ZUPTZBDBwJx3dg18Q4h50Sp5PKj6rtNRUTXNOJqwzr5BDRWYLgpxqt7ZeSNJ2ysD4" alt="[圖]" /></div>
點擊檔案會顯示可供連結的網址,把權限改成「Public Read」可以供所有人讀取
<div class="img" data-ori_w="610" data-ori_h="466" style="width:436px;height:333px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/YxJkA2rJ4oEedRKxG9pGIjal0LEs_iNK9m5McNRzwuuGGO5kK9JlAXWZbbckyTBuOKY7rwca0292v3xDjC1jQIwIFB38Nl4G5TDODOzU0boj_QVRSdFbxbzfgKgtpQPTLp1TSMdAG0BhS7UnbkEeNtk" alt="[圖]" /></div>
在這邊建立一組 Access Key 和 Secret,用來供其他程式存取選擇的 Bucket
<div class="img" data-ori_w="701" data-ori_h="153" style="width:501px;height:109px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/f7qdmJizvdZ_5ymRFgLWQkwAvLOZGOQSC43AMh33B7XZkA_2bxTkJ3A0rMCRT1frUzeFsyeJwXC6HHhb71sT5x5RMeHpdPV755BaUKKVh1pOBkm3TWsJ6ZlM9vyPbZfxp37tx8HPnSOEdUY1jJ1xXaY" alt="[圖]" /></div>
<div class="img" data-ori_w="925" data-ori_h="699" style="width:661px;height:499px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/BYJzGe5ZDoXvJoHcOSlPiM0DFKXmgMO2gmZ_oZe6NCHXN2DN9zgj03FTqL8_rv1PVLVZV-xwimmPn0f8XZHw4l_Gul8JY4-GFpSAm8R_W2kxfMgjjTwe7-JIMP4szje8nXDsgStpCqcSUjttn9hgkfs" alt="[圖]" /></div>
# <span style="color:#00F000">使用 Cyberduck</span>
參考: <a href="https://www.linode.com/docs/products/storage/object-storage/guides/cyberduck/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/storage/object-storage/guides/cyberduck/</a>
官網下載: <a href="https://cyberduck.io/download/" target="_blank" rel="nofollow">https://cyberduck.io/download/</a>
下載安裝 Cyberduck for Windows 8.7.1
在「編輯」/「偏好設定」,點「Profiles」,搜尋「Linode」
勾選要使用的 Data Center,例如 Linode Object Storage (Osaka, JP)
<div class="img" data-ori_w="1056" data-ori_h="503" style="width:754px;height:359px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/q2eOnS8RIMQrGb9RSNBfo0jM8-RUtq-VW_yRIZk_XKLN2AWCh4_1ahFD9rS0gNFFgXXC9IKVfiRL8PnTv58w68CSaqy5bOurkrc-Sn9OB1DTs0-T9kDeQ6bVZLim2v0K_gr-lrmq3DIncv5Dh0Krph0" alt="[圖]" /></div>
點「新增連線」,設定檔選「Linode Object Storage (Osaka, JP)」
輸入 Access key 與 Secret 後按「連線」
<div class="img" data-ori_w="800" data-ori_h="648" style="width:571px;height:463px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/ZJRK00iE2vPxpmv_biJvtvy8CUOzEnPywHPJDW0cz1Bfq7qsrhoVCVJLOClhvpPOwFs4QoFQ7nxB5GIvdio5HxzInJsa3kYRONChQKk9g2kbnzlI7-jyCql1_u_6bCqYYyrNYMq2tH8chlNHtAqzmpY" alt="[圖]" /></div>
<div class="img" data-ori_w="846" data-ori_h="346" style="width:604px;height:247px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/FQ5N5SglJHLElxyI3dZWKxa290xXQCnr7nljWnEZwlN09IVlm5jCVdjlwnfP0gilr_dJC3VWebJNbbKZ_rde7cQEYmwhnFXRyajNOEBPK8zwQm-syhAWHH1hL53GMtWPxaBEmpq_ZPmmKHJ5yObbmew" alt="[圖]" /></div>
選取檔案後點「簡介」,可以取得網址
<div class="img" data-ori_w="699" data-ori_h="556" style="width:499px;height:397px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/RZSeqobiOqCY7fjpZFCL0W2CEF-AyIc8RpB3dad_P_lQqy6Iqap9R20-QyvyAnuATCpW_jfLIPXHDqYUc547gGKJ7M7r3cEIIauj4GMygeHzM-A9iPIgN0o8adj1eZOvw4MiKzqtBAGPDBLzQPNOWN8" alt="[圖]" /></div>
# <span style="color:#00F000">自訂連結網址</span>
參考
<a href="https://www.linode.com/docs/products/storage/object-storage/guides/custom-domain/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/storage/object-storage/guides/custom-domain/</a>
要使用自訂連結的話,Bucket Label 要使用自訂連結的網址,才能使用 SSL 憑證
例如想要用 <a href="https://s3.disp.cc/test/knuckles.png" target="_blank" rel="nofollow">https://s3.disp.cc/test/knuckles.png</a> 這樣的網址來讀取檔案的話,Bucket Lable 要設定為「s3.disp.cc」,但要注意這樣 s3.disp.cc.jp-osa-1.linodeobjects.com 的網址就不能使用 https 連線。
修改 DNS 設定,加上 CNAME 記錄,例如使用 Linode Domains 的話,「Hostname」輸入「s3」,「Alias to」輸入「s3.disp.cc.jp-osa-1.linodeobjects.com」
<div class="img" data-ori_w="613" data-ori_h="488" style="width:438px;height:349px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/83TipU7c-JSJRhfIKyEn_hGekG4sQYMMhO1FXCV-GEJMIzcO0nIWgj85_szAZ492EuN35KCZOMLkVmIvHbSI-8vpTI6YIAqrk5TGniIwlwYO78wojSwkNIomkc_4GxCjOTVx2YmTb63V1anEagFW_CI" alt="[圖]" /></div>
等待 DNS 更新後,就可以用非加密的 <a href="http://s3.disp.cc/test/knuckles.png" target="_blank" rel="nofollow">http://s3.disp.cc/test/knuckles.png</a> 讀取檔案了
如果要使用 https 加密連線的話,要申請 SSL 憑證,例如使用 certbot 申請 Let's Encrypt 憑證,執行
<code class="code-inline">$ sudo certbot certonly --manual</code>
依顯示的步驟手動完成 HTTP-01 Challenge,要自己將驗證檔上傳到 Bucket,讓 Let's Encrypt 主機驗證所有權,然後將產生的憑證貼在 Bucket 的「SSL/TLS」
「Certificate」輸入 /etc/letsencrypt/live/s3.disp.cc/fullchain.pem 的內容
「Private Key」輸入 /etc/letsencrypt/live/s3.disp.cc/privkey.pem 的內容
<div class="img" data-ori_w="1119" data-ori_h="433" style="width:799px;height:309px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/nGAwJ1_BhjF_V7AaqBafrhPp2J3iimFrGo8T34mtqFE_ZNLcbIkjK1PjxI3qrbssB8VxUC3nEA-QL1SNpzFrJ_ZT3yUViR-o8_X1JZa30z4tRhx5M5o8kOtKRxsMZnwGYtJwTjnhJ10wOBqkKgyL2NU" alt="[圖]" /></div>
但這樣之後不能自動更新憑證
# <span style="color:#00F000">自動更新憑證</span>
參考這篇 <a href="https://disp.cc/b/KnucklesNote/gfCN" target="_blank" rel="nofollow">[Linode] NodeBalancer 使用 Let's Encrypt SSL憑證 - KnucklesNote板 - Disp BBS</a>
使用 DNS-01 Challenge 申請 *.disp.cc 萬用字元憑證(Wildcard Certificate)
在 Linode 產生一個 API Token,Label 取個名稱,Expiry 選 Never,點一下 Select All 的 None,然後點 Account 的 Read Only,點 Object Storage 的 Read/Write,其他權限視需要再增加
<div class="img" data-ori_w="592" data-ori_h="880" style="width:423px;height:629px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/6xpJwLFOqAD-WR2clGjPLc_Q-QhyrmHXdPWm--OX2W0rNhr67Mt5opYAPkygvAjBm_baE1qYPJkrGBpotovcbH0KMjC1adVxkPh4R6Q3S2t2VpVD5ZyuD429G5F-cW5xpnygdqgyw1NsFJ1QzQ_G8qA" alt="[圖]" /></div>
複製 Personal Access Token
<div class="img" data-ori_w="811" data-ori_h="433" style="width:579px;height:309px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/9Ad05_sXIc2D-PzBToUdZ28FDwTtR5vw8I_OSmzVgg6HLHwV5GkqsPBwYVuFGYI3cbDvXlW35eU8NNOiZQEOHlTsWP-fMtqsgPtJrf5njkwVnFFxRK4Rvy1vuv3O1tyjSUTGj603vTCL1jxzqQXknDA" alt="[圖]" /></div>
使用 pip3 安裝 linode-cli
<code class="code-inline">$ sudo yum install python3 python3-pip</code>
<code class="code-inline">$ sudo pip3 install linode-cli --upgrade</code>
<code class="code-inline">$ sudo ln -s /usr/local/bin/linode-cli /usr/bin/linode-cli</code>
設定 linode-cli
<code class="code-inline">$ linode-cli configure</code>
依提示輸入剛剛產生的 Personal Access Token
已輸入過的話也可以編輯 ~/.config/linode-cli 檔就好
將萬用字元憑證使用 linode-cli 上傳至 Object Storage
<code class="code-inline">$ sudo vim /root/update_linode.sh</code>
<div class="highlight"><span></span><span class="ch">#!/bin/bash</span>
<span class="nv">region</span><span class="o">=</span>jp-osa-1
<span class="c1"># 輸入每個要更新憑證的 bucket,以空格分隔</span>
<span class="nv">Buckets</span><span class="o">=</span><span class="s2">"s3.disp.cc img.disp.cc"</span>
<span class="nv">cert</span><span class="o">=</span><span class="k">$(</span></etc/letsencrypt/live/disp.cc/fullchain.pem<span class="k">)</span>
<span class="nv">key</span><span class="o">=</span><span class="k">$(</span></etc/letsencrypt/live/disp.cc/privkey.pem<span class="k">)</span>
<span class="k">for</span><span class="w"> </span>bucket<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="si">${</span><span class="nv">Buckets</span><span class="si">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span>
<span class="w"> </span>linode-cli<span class="w"> </span>object-storage<span class="w"> </span>ssl-delete<span class="w"> </span><span class="nv">$region</span><span class="w"> </span><span class="nv">$bucket</span>
<span class="w"> </span>linode-cli<span class="w"> </span>object-storage<span class="w"> </span>ssl-upload<span class="w"> </span><span class="nv">$region</span><span class="w"> </span><span class="nv">$bucket</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--certificate<span class="w"> </span><span class="s2">"</span><span class="nv">$cert</span><span class="s2">"</span><span class="w"> </span>--private_key<span class="w"> </span><span class="s2">"</span><span class="nv">$key</span><span class="s2">"</span>
<span class="k">done</span>
</div>
設定 crontab 定期更新,例如每週5的3:45執行一次
<code class="code-inline">$ vim /etc/crontab</code>
45 3 * * 5 root /root/update_linode.sh
參考
<a href="https://www.linode.com/docs/products/tools/cli/guides/object-storage/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/tools/cli/guides/object-storage/</a>
<a href="https://www.linode.com/docs/api/object-storage/#object-storage-tlsssl-cert-upload" target="_blank" rel="nofollow">https://www.linode.com/docs/api/object-storage/#object-storage-tlsssl-cert-upload</a>
錯誤解決記錄:
新增 SSL/TLS 的時候出現
Certificate is not valid for the bucket name. Verify the bucket name is covered by the SANs and/or CN.
→ Bucket Label 要與 SSL 憑證的網址相同才行
# <span style="color:#00F000">使用 Linode CLI 存取檔案</span>
Linode CLI 只有一些簡單的操作,可以使用下一節的 s3cmd 就好
依前一節的說明用 pip3 安裝 linode-cli
安裝 boto 函式庫,就可以使用 obj 擴充功能
<code class="code-inline">$ sudo pip3 install boto boto3</code>
執行看看
<code class="code-inline">$ linode-cli obj</code>
第一次執行會詢問預設的區域,選 jp-osa-1
obj 可用的指令:
mb 建立一個新的 bucket
rb 刪除一個 bucket
du 顯示每個 bucket 占用的空間與檔案數,以及總占用的空間
la 列出所有 buckets 上的所有檔案
ls 列出 buckets 或 bucket 上的檔案
put 上傳一個檔案到 bucket
get 下載一個 bucket 上的檔案
del 刪除 bucket 上的一個檔案
rm 刪除 bucket 上的一個檔案
setacl 設定檔案的存取限制
指令加 - h 可顯示說明,例如顯示 ls 的說明
<code class="code-inline">$ linode-cli obj ls -h</code>
列出目前已建立的 buckets
<code class="code-inline">$ linode-cli obj ls</code>
列出 my-bucket 的 test 資料夾中的檔案
<code class="code-inline">$ linode-cli obj ls my-bucket/test/</code>
上傳檔案 test.txt 到 my-bucket 的 test 資料夾
<code class="code-inline">$ linode-cli obj put --acl-public test.txt my-bucket/test/</code>
加上 --acl-public 代表設定為任何人都可以讀取
下載 my-bucket 上的檔案 test/test.txt
<code class="code-inline">$ linode-cli obj test my-bucket test/test.txt</code>
刪除 my-bucket 上的檔案 test/test.txt
<code class="code-inline">$ linode-cli obj rm my-bucket test/test.txt</code>
參考
<a href="https://www.linode.com/docs/products/storage/object-storage/guides/linode-cli/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/storage/object-storage/guides/linode-cli/</a>
# <span style="color:#00F000">使用 s3cmd 存取檔案</span>
<a href="https://s3tools.org/s3cmd" target="_blank" rel="nofollow">s3cmd</a> 是 Amazon S3 用來操作檔案存取的工具,與 S3 相容的服務也可以使用,比 Linode CLI 有更多的功能可以使用。
使用 python 安裝 s3cmd
<code class="code-inline">$ sudo pip3 install s3cmd</code>
<code class="code-inline">$ sudo ln -s /usr/local/bin/s3cmd /usr/bin/s3cmd</code>
輸入 s3cmd 設定值
<code class="code-inline">$ s3cmd --configure</code>
Access Key: 輸入之前產生的 Access Key
Secret Key: 輸入之前產生的 Secret
Default Region: US 使用預設值就好,不要更改
S3 Endpoint (cluster URL): 輸入 jp-osa-1.linodeobjects.com
DNS-style bucket+hostname: 輸入 %(bucket)s.jp-osa-1.linodeobjects.com
Encryption password: 不用填
Path to GPG program: 不用填
Use HTTPS protocol: Yes
HTTP Proxy server name: 不用填
HTTP Proxy server port: 不用填
Test access: 目前使用測試會失敗,先按 n 跳過
Save settings? 按 y 儲存設定
設定值會儲存在 ~/.s3cfg
列出、建立、刪除 Bucket
<code class="code-inline">$ s3cmd ls</code>
<code class="code-inline">$ s3cmd mb s3://my-bucket</code>
<code class="code-inline">$ s3cmd rb s3://my-bucket</code>
如果 bucket 中有檔案的話,加上 -r (--recursive) 與 -f (--force) 可強制刪除
<code class="code-inline">$ s3cmd rb -r -f s3://my-bucket</code>
列出 my-bucket 中 test 資料夾的檔案
<code class="code-inline">$ s3cmd ls s3://my-bucket/test/</code>
上傳檔案 test.txt 到 my-bucket 的 test 資料夾
<code class="code-inline">$ s3cmd put -P test.txt s3://my-bucket/test/</code>
加上 -P (--acl-public) 代表設定檔案權限為公開
下載、刪除 my-bucket 上的檔案 test/test.txt
<code class="code-inline">$ s3cmd get s3://my-bucket/test/test.txt</code>
<code class="code-inline">$ s3cmd rm s3://my-bucket/test/test.txt</code>
刪除 my-bucket 上的 test/ 資料夾與裡面的所有檔案
<code class="code-inline">$ s3cmd rm -r s3://my-bucket/test/</code>
加 -r (--recursive) 代表資料夾中的所有檔案
複製、移動 my-bucket 的 test/test.txt 到 my-bucket2 的 test2/
<code class="code-inline">$ s3cmd cp s3://my-bucket/test/test.txt s3://my-bucket2/test2/</code>
<code class="code-inline">$ s3cmd mv s3://my-bucket/test/test.txt s3://my-bucket2/test2/</code>
設定 my-bucket 的 test/ 資料夾中的檔案權限為公開
<code class="code-inline">$ s3cmd setacl -P -r s3://my-bucket/test/</code>
同步資料夾 test/ 中的檔案到 my-bucket 的 test/ 資料夾
<code class="code-inline">$ s3cmd sync -P test/ s3://my-bucket/test/</code>
或是
<code class="code-inline">$ s3cmd sync -P test s3://my-bucket/</code>
使用 sync 只會上傳有更新或新增的檔案
加 -P (--acl-public) 代表設定權限為公開
參考
<a href="https://www.linode.com/docs/products/storage/object-storage/guides/s3cmd/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/storage/object-storage/guides/s3cmd/</a>
使用 PHP 讀寫檔案,參考下一篇
<a href="https://disp.cc/b/KnucklesNote/gLmU" target="_blank" rel="nofollow">[Linode] Object Storage 使用 AWS SDK for PHP - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2024-01-09 03:22:31 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2024-01-09 04:12:49 (台灣)</span></div></pre>
Knuckles
[Apache] 讀取log檔的流量分析工具 GoAccess
http://disp.cc/b/11-gF6f
2023-11-24T22:48:03+08:00
2023-11-26T04:09:15+08:00
Apache 預設的 log 檔的每一筆連線記錄是像這樣
42.73.1.15 - - [23/Nov/2023:18:06:11 +0800] "GET /b/Gossiping/gENS HTTP/1.1" 200 8214 "https://disp.cc/b/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
直接看 log 檔的話很難看出有什麼問題,必需使用分析工具才行
例如使用 GoAccess 來分析 Apache 的 log 檔
官網: https://goaccess.io/
安裝環境: CentOS 7, RockyLinux 9
# 安裝 GoAccess
使用 yum 安裝 GoAccess
$ sudo yum install goaccess
CentOS 7 會安裝 goaccess.x86_64 0:1.3-1.el7 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Apache] 讀取log檔的流量分析工具 GoAccess<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-11-24 Fri. 22:48:03</div><hr color="#008080" />
Apache 預設的 log 檔的每一筆連線記錄是像這樣
42.73.1.15 - - [23/Nov/2023:18:06:11 +0800] "GET /b/Gossiping/gENS HTTP/1.1" 200 8214 "https://disp.cc/b/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
直接看 log 檔的話很難看出有什麼問題,必需使用分析工具才行
例如使用 GoAccess 來分析 Apache 的 log 檔
<div class="img" data-ori_w="1368" data-ori_h="911" style="width:684px;height:456px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/H1A8hUgD21m1xTbJiCWYUY5fKKn85dkwnrtKXX4F5rpaGQ8kRJH3zMdOs-xMg5pjibLF4jGnVC6cOjwC1n5vVb_x1ArEgIan9j4s1MEVr0nujkLTZdu0ByG5jxUqHQKfL0pZVMIVIrKbAoIQJZb-SaA" alt="[圖]" /></div>
官網: <a href="https://goaccess.io/" target="_blank" rel="nofollow">https://goaccess.io/</a>
安裝環境: CentOS 7, RockyLinux 9
# <span style="color:#00F000">安裝 GoAccess</span>
使用 yum 安裝 GoAccess
<code class="code-inline">$ sudo yum install goaccess</code>
CentOS 7 會安裝 goaccess.x86_64 0:1.3-1.el7
RockyLinux 9 會安裝 goaccess-1.8.1-1.el9.x86_64
查看安裝的版本
<code class="code-inline">$ goaccess -V</code>
GoAccess - 1.3.
For more details visit: <a href="http://goaccess.io" target="_blank" rel="nofollow">http://goaccess.io</a>
Copyright (C) 2009-2016 by Gerardo Orellana
Build configure arguments:
--enable-debug
--enable-utf8
--enable-geoip=legacy
--enable-tcb=memhash
--with-getline
--with-openssl
目前官網上的版本為 1.8 版,若是用 yum 安裝的版本為 1.3 太舊了會有點問題,可以改用官網下載安裝,而且查詢 IP 所在地的 GeoIP legacy 已停止更新了,要換成新的 GeoIP mmdb,所以先移除 yum 安裝的
<code class="code-inline">$ sudo yum remove goaccess</code>
如果要使用 <code class="code-inline">--enable-geoip=mmdb</code>
需要先安裝 <a href="https://github.com/maxmind/libmaxminddb" target="_blank" rel="nofollow">https://github.com/maxmind/libmaxminddb</a>
點右邊的 Releases,複製新版的 libmaxminddb 下載連結
<code class="code-inline">$ cd /usr/local/src</code>
<code class="code-inline">$ sudo wget <a href="https://github.com/maxmind/libmaxminddb/releases/download/1.8.0/libmaxminddb-1.8.0.tar.gz" target="_blank" rel="nofollow">https://github.com/maxmind/libmaxminddb/releases/download/1.8.0/libmaxminddb-1.8.0.tar.gz</a></code>
<code class="code-inline">$ sudo tar -zxvf libmaxminddb-1.8.0.tar.gz</code>
<code class="code-inline">$ cd libmaxminddb-1.8.0</code>
<code class="code-inline">$ sudo ./configure</code>
<code class="code-inline">$ sudo make</code>
<code class="code-inline">$ sudo make install</code>
<code class="code-inline">$ sudo sh -c "echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf"</code>
<code class="code-inline">$ sudo ldconfig</code>
下載安裝 GoAccess
<code class="code-inline">$ cd /usr/local/src</code>
<code class="code-inline">$ sudo wget <a href="https://tar.goaccess.io/goaccess-1.8.1.tar.gz" target="_blank" rel="nofollow">https://tar.goaccess.io/goaccess-1.8.1.tar.gz</a></code>
<code class="code-inline">$ sudo tar -xzvf goaccess-1.8.1.tar.gz</code>
<code class="code-inline">$ cd goaccess-1.8.1/</code>
<code class="code-inline">$ sudo ./configure --enable-utf8 --enable-geoip=mmdb --with-getline --with-openssl</code>
<code class="code-inline">$ sudo make</code>
<code class="code-inline">$ sudo make install</code>
執行檔會安裝在 <code class="code-inline">/usr/local/bin/goaccess</code>,若沒有在執行路徑的話,加個軟連結
<code class="code-inline">$ ln -s /usr/local/bin/goaccess /usr/bin/goaccess</code>
檢查版本
<code class="code-inline">$ goaccess -V</code>
GoAccess - 1.8.1.
For more details visit: <a href="https://goaccess.io/" target="_blank" rel="nofollow">https://goaccess.io/</a>
Copyright (C) 2009-2023 by Gerardo Orellana
Build configure arguments:
--enable-utf8
--enable-geoip=mmdb
--with-getline
--with-openssl
# <span style="color:#00F000">安裝 GeoIP</span>
GeoIP 是 MaxMind 提供的 IP 所在地資料庫,只要註冊帳號就可以使用免費版的 GeoLite2,每段時間會固定更新資料,可以手動下載或是安裝自動更新程式
資料庫有三種,ASN 是公司單位名稱,City 是國家與城市,Country 是只有國家
要手動下載的話,到 MaxMind 登入後下載 GeoLite2 City (.mmdb)
<a href="https://www.maxmind.com/en/accounts/current/geoip/downloads" target="_blank" rel="nofollow">https://www.maxmind.com/en/accounts/current/geoip/downloads</a>
<div class="img" data-ori_w="1600" data-ori_h="846" style="width:800px;height:423px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/WZq7D83_UEhNZICJhh6QkHZT8cehBT3Q_taHcqhY_Gs7FNC2okajtQhNdIznRo4GsGyC6zXn-lt9bo0v1902NZN68RoezW8T1sARU1QSLivgY1NB_SBzK34YKeMTA31pZR_5XHv2djnWndvvZKSCHO4" alt="[圖]" /></div>
下載到 /usr/local/share 後解壓縮
<code class="code-inline">$ sudo tar -zxvf GeoLite2-City_20231121.tar.gz</code>
更改資料夾名稱為 <code class="code-inline">GeoIP</code>
<code class="code-inline">$ sudo mv GeoLite2-City_20231121 GeoIP</code>
資料庫檔案的路徑為 <code class="code-inline">/usr/local/share/GeoIP/GeoLite2-City.mmdb</code>
要使用自動更新的話,到 MaxMind 登入後取得 license-key
<a href="https://www.maxmind.com/en/accounts/925914/license-key/" target="_blank" rel="nofollow">https://www.maxmind.com/en/accounts/925914/license-key/</a>
<div class="img" data-ori_w="936" data-ori_h="643" style="width:468px;height:322px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/Ci3n1KV7nfRkESLm3bXUiWkCbarwlQl9QATxcjbGRomEXrFyX-NfQnWgQykUgPW7qqbHruqqTJq5KIwuyJbrxO0SUIs5jyqiqBQAV9b9HQxXtyTZS1AEEATc7JXNSnj68Ru_WZyuxIN1VnYcQSoKMsA" alt="[圖]" /></div>
點「Generate new license key」後,取個名字,例如「GoAccess」,點「Confirm」
<div class="img" data-ori_w="1204" data-ori_h="432" style="width:602px;height:216px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/V9H9zJV4TmwpO9oRVLkinBlLXtlkpeMkLPCzBb3qgEQNo0GVMKLNWO2fM6RrkmezeHwIvW5dNqk0GBptIscUykS5Rbsylrrk2MVbYsmw7FBj3Qc_rA30Y8-E2Ds7jobVj8pH_I4ITWAAon7GaFoOCio" alt="[圖]" /></div>
複製 Account ID 和 License key,或是點「Download Config」下載設定檔
<div class="img" data-ori_w="1199" data-ori_h="839" style="width:600px;height:420px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/vS-YA1ULq9gOn5VWgsVL6sMVdktX_DT9W3_YxvzQlUuf12tEGwzcEL-iuVNb-jFRASfFIjxxqBvXf4DFth1zy_JxnSpmwFV51MViZcpgiQQ2CzwqCmzNxA2tDbHn6pn0Vz0tFdelPzemMNeofhvYMeM" alt="[圖]" /></div>
下載新版的 geoipupdate,到 GitHub 的 Releases 頁
<a href="https://github.com/maxmind/geoipupdate/releases" target="_blank" rel="nofollow">https://github.com/maxmind/geoipupdate/releases</a>
複製新版的 rpm 下載連結,例如目前是 geoipupdate_6.0.0_linux_amd64.rpm
安裝 geoipupdate
<code class="code-inline">$ cd /usr/local/src</code>
<code class="code-inline">$ sudo wget <a href="https://github.com/maxmind/geoipupdate/releases/download/v6.0.0/geoipupdate_6.0.0_linux_amd64.rpm" target="_blank" rel="nofollow">https://github.com/maxmind/geoipupdate/releases/download/v6.0.0/geoipupdate_6.0.0_linux_amd64.rpm</a></code>
<code class="code-inline">$ sudo rpm -Uvhi geoipupdate_6.0.0_linux_amd64.rpm</code>
修改設定檔,輸入取得的 Account ID 和 License key
<code class="code-inline">$ sudo vim /etc/GeoIP.conf</code>
<div class="highlight">AccountID YOUR_ACCOUNT_ID_HERE
LicenseKey YOUR_LICENSE_KEY_HERE
EditionIDs GeoLite2-ASN GeoLite2-City GeoLite2-Country
</div>
執行自動更新
<code class="code-inline">$ sudo geoipupdate -v</code>
其中 -v 代表顯示更新過程
資料庫檔案的路徑為 <code class="code-inline">/usr/share/GeoIP/GeoLite2-City.mmdb</code>
設定一週更新一次,例如每週1的2:34更新
<code class="code-inline">$ vim /etc/crontab</code>
34 2 * * 1 root /usr/bin/geoipupdate
# <span style="color:#00F000">執行流量分析</span>
執行 goaccess 輸入要分析的 log 檔
<code class="code-inline">$ goaccess access.log</code>
選擇記錄檔的格式,若是 Apache 設定中 CustomLog 是使用預設的 combined,
例如: <code class="code-inline">CustomLog "/var/log/httpd/access.log" combined</code>
那就選第一個 NCSA Combined 就好,按空白鍵選擇後按 Enter
<div class="img" data-ori_w="792" data-ori_h="540" style="width:396px;height:270px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/LYESNXZqBVxrd9fNzLnPNAelZFQgd9ZPBBQeK7x2hFdin9eetRvj9jRMCihQjCG2vNggPU7nd9Q0ccq9L65uj6AGRoVQ7KGEi75-PTq7ZAiJ1ks2Yw4o_5kONYb_vxb4ekId-Ew5-SEQ7ml_8tnZOsk" alt="[圖]" /></div>
等待分析完成
[Parsing access.log] {1,080,211} @ {30,863/s}
執行的畫面,會即時更新新增的記錄
<div class="img" data-ori_w="1366" data-ori_h="907" style="width:683px;height:454px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/zg30bKeqovZtlrhnmxuGCiMRtXQE3ZWHumA6H-g2aC0pRp18OJD5VcEZix3koj52I321BfUtoPTvRuSkhfgRks7ct2zt_qT1mdWpHVG2B68zp70Nj3AdAevmcXS5YPCUu8VCaa09hSESzJ6_uThcZu8" alt="[圖]" /></div>
按上下鍵或數字鍵可切換到其他項目,按o或→看詳細資料,按 q 離開
要到第10項之後的話按 shift+數字鍵,例如 shift+1 會跳到第11項
1 - Unique visitors per day - Including spiders 每天有多少不同的IP造訪,包含爬蟲
2 - Requested Files (URLs) 瀏覽的動態網頁
3 - Static Requests 瀏覽的靜態網頁
4 - Not Found URLs (404s) 找不到的網頁
5 - Visitor Hostnames and IPs 造訪量最高的 IP
6 - Operating Systems 使用的作業系統
7 - Browsers 使用的瀏覽器
8 - Time Distribution 依每個小時(00~23)的流量分佈
10 - Referrers URLs 來源網址
11 - Referring Sites 來源網站
12 - Keyphrases from Google's search engine 搜尋來的關鍵字
13 - HTTP Status Codes 狀態碼
16 - Geo Location 來源的地區
17 - ASN 來源的自治系統編號與自治組織的名稱
要排除搜尋引擎爬蟲的話
<code class="code-inline">$ goaccess access.log --ignore-crawlers</code>
可以一次輸入多個 log 檔
<code class="code-inline">$ goaccess access.log*</code>
<div class="img" data-ori_w="1368" data-ori_h="911" style="width:684px;height:456px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/H1A8hUgD21m1xTbJiCWYUY5fKKn85dkwnrtKXX4F5rpaGQ8kRJH3zMdOs-xMg5pjibLF4jGnVC6cOjwC1n5vVb_x1ArEgIan9j4s1MEVr0nujkLTZdu0ByG5jxUqHQKfL0pZVMIVIrKbAoIQJZb-SaA" alt="[圖]" /></div>
如果 log 檔被壓縮成 gz 檔,可以改用
<code class="code-inline">$ zcat access.log.*.gz | goaccess</code>
有指定 GeoLite2-City.mmdb 的話,第5項的詳細畫面會顯示 IP 的國家、地區
<code class="code-inline">$ goaccess access.log --geoip-database /usr/local/share/GeoIP/GeoLite2-City.mmdb</code>
<div class="img" data-ori_w="986" data-ori_h="643" style="width:493px;height:322px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/IKWYWLlUZg4B4gtIfWvRN5C-zLMh5viTOhdvJW5l0TB98eDf2SRZ-g_Ki2TMQ_bM10YRKm1pjbX3YWvNyY_kHXEMoDCdD6ODtLJpiuvbqZGtO6LpU0N26BTJeh_tQbPOqAWHssd5qc_TXWkPiYLC5Pc" alt="[圖]" /></div>
有指定 GeoLite2-ASN.mmdb 的話,第5項的詳細畫面會顯示自治組織
<code class="code-inline">$ goaccess access.log --geoip-database /usr/share/GeoIP/GeoLite2-ASN.mmdb</code>
<div class="img" data-ori_w="1209" data-ori_h="751" style="width:605px;height:376px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/Vw5foM1uR8lcSNJUWuQDmtpZyNvpmNSHp9cqqfitTucCeMjzK4QHbXlyIDjATCOqfxCfzLTCR3UScUBeaoHDp9v4LzDXN9Ivnazz01X0Qxu4Ogb4aDYg4B05GD9YyNajwSIoFMc_905xI5VGvCO3Nqg" alt="[圖]" /></div>
同時指定 ASN 和 City 就可以都顯示,但會增加分析時間
<code class="code-inline">$ goaccess access.log --geoip-database /usr/share/GeoIP/GeoLite2-ASN.mmdb --geoip-database /usr/share/GeoIP/GeoLite2-City.mmdb</code>
<div class="img" data-ori_w="1063" data-ori_h="647" style="width:532px;height:324px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/cgRv6ctGSznForI2EhnUbXwaYG2at-8H60QhSbh4j-IMYcGTFjN3z66cmrKElbC6MriHVt5e7JIXiAj_0v8ZnzrZRYF8Yx9PAOMToPVlPRiQRazdvYSybuDoDKX7yfzGbchvkl8ta1B7ma6NwBAOSEY" alt="[圖]" /></div>
有指定 GeoLite2-ASN.mmdb 的話,第17項會統計來源的自治組織 (Autonomous System, AS),通常每個 AS 都是由單一大型組織運作,例如網際網路服務提供者 (ISP)、大型企業科技公司、大學或政府機構。
<div class="img" data-ori_w="1095" data-ori_h="743" style="width:548px;height:372px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/opofzTsE1XpGVFAW6tLxrXDWN_4Af-WnWYZP671yu1FotCcfQoPw0ZjHhc4dVdw8EAT1ZBjooYfmvoXoJ73stpTAYz5smKmLGAMru73qaVb7c_q24taiFB5s8rSj4f__X05Y3_JEnVsFSevKFfWgUGI" alt="[圖]" /></div>
有加 -a 參數的話,會產生每個 IP 的 user-agent 列表,但會增加分析的時間
<code class="code-inline">$ goaccess access.log -a</code>
在第5項的詳細資料,用 j k 上下選擇 IP 後按 Enter
<div class="img" data-ori_w="1340" data-ori_h="481" style="width:670px;height:241px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/IB1pgnfx33AlQl6U6Hu45aRHMZG1EmPFChHQr5-Myk-xKOUHin_ubT0ZrFQB5w67FOFEFxA-9_mhaJg-UYvvVKUUF6b7QYL7vjrCeuCkxfJ7c0xoRHZw8Q7KVq7ILTlgKj4WkTF2jQpV_WibBExxcqk" alt="[圖]" /></div>
第5項很適合用來抓造成異常流量的爬蟲機器人,例如下圖,發現一個香港的 IP 流量特別高,而且每秒就新增好幾個連線,打開記錄檔搜尋這個 IP 看看都在抓什麼,確認不是正常的連線後,就可以在防火牆將他擋掉
<div class="img" data-ori_w="1266" data-ori_h="602" style="width:633px;height:301px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/L31JsFrDmfX-y2SMInMTFa3RdCPOQY54_Ve3udPRVjs9m0z5ApeJ3tRAK62NbmOueY6UHeBeoZwn_ywt9pEhzc09mTFKh4dfT4cqkrzpIlbAmn5UrfeddMjjLU-jk3RBLL9gsyDL54r_0Ghl5xP1Xwk" alt="[圖]" /></div>
# <span style="color:#00F000">產生網頁檔的流量分析</span>
<code class="code-inline">$ goaccess access.log* -o /var/www/html/report.html --log-format=COMBINED</code>
想要即時更新的話加上 <code class="code-inline">--real-time-html</code>
用瀏覽器打開來看
<div class="img" data-ori_w="1447" data-ori_h="1111" style="width:724px;height:556px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/oIhlaAgnV9dthyMVdOUmHSRUG2c73jyeiiCjcD49KF83H6ZNwuRKkUoDbeV8LmxjoaT30aI2gbAvnQv3XmTCcFjszRkfMYv0-RRFfWktInDj07nZGj4s2jCAFF-9KRaxbzpFXJL2jLcNWIWmUMvvvIM" alt="[圖]" /></div>
最多造訪次數的IP資料,有加參數 -a 的話可以按左邊的箭頭顯示 user-agent 清單
<div class="img" data-ori_w="1600" data-ori_h="1508" style="width:800px;height:754px"><img style="max-width:100%;" data-ratio="2" src="https://lh7-us.googleusercontent.com/kdm9MW4ALnWzC3A2nfIv5diNgr87WUCGpaa3Ry70cNEnaTcdnPbCW8x1XUJB9DqZ63ynyhOQSyeZW5gZbytbPTi5pjIZmDdQEZm72Mf20tcguyQku2fpNPB7UM8n_FIdtX_RhRoViGmQAto2OsXXxkw" alt="[圖]" /></div>
# <span style="color:#00F000">修改設定檔</span>
執行 <code class="code-inline">$ goaccess --dcf</code> 查詢設定檔的位置
/etc/goaccess/goaccess.conf
使用編譯安裝的話會在
/usr/local/etc/goaccess/goaccess.conf
編輯設定檔
<code class="code-inline">$ sudo vim /usr/local/etc/goaccess/goaccess.conf</code>
<div class="highlight"><span></span><span class="c"># 預設的時間格式</span>
<span class="c"># Time Format Options (required)</span>
<span class="c"># Apache/NGINX's log formats below.</span>
<span class="c">#time-format %H:%M:%S</span>
time-format <span class="s">%H</span><span class="p">:</span><span class="s">%M</span><span class="p">:</span><span class="s">%S</span>
<span class="c"># 預設的時期格式</span>
<span class="c"># Date Format Options (required)</span>
<span class="c"># Apache/NGINX's log formats below.</span>
<span class="c">#date-format %d/%b/%Y</span>
date-format <span class="s">%d/%b/%Y</span>
<span class="c"># 設定預設的 Log 檔格式,之後就不用再輸入 --log-format</span>
<span class="c"># Log Format Options (required)</span>
<span class="c"># NCSA Combined Log Format</span>
<span class="c">#log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"</span>
log-format <span class="s">%h %^[%d</span><span class="p">:</span><span class="s">%t %^] "%r" %s %b "%R" "%u"</span>
<span class="c">#第10和12項沒顯示的話,把這兩行註解掉</span>
<span class="c">#ignore-panel REFERRERS</span>
<span class="c">#ignore-panel KEYPHRASES</span>
<span class="c">#設定下載的 GeoIP 資料庫檔案路徑,之後不用再指定 --geoip-database</span>
<span class="c">#geoip-database /usr/local/share/GeoIP/GeoLiteCity.dat</span>
geoip-database <span class="s">/usr/share/GeoIP/GeoLite2-City.mmdb</span>
<span class="c">#想要預設顯示自治組織可以再加這個,但會增加分析時間</span>
<span class="c">#geoip-database /usr/share/GeoIP/GeoLite2-ASN.mmdb</span>
</div>
錯誤解決記錄:
GoAccess 1.3 版 修改設定檔無效,執行 $ goaccess --dcf 會顯示
No default config file found.
也無法使用 -p 指定設定檔路徑
→ 自行安裝新的 1.8 版
編譯安裝時出現錯誤訊息 goaccess Missing development files for libmaxminddb library.
→ 要先安裝 <a href="https://github.com/maxmind/libmaxminddb" target="_blank" rel="nofollow">https://github.com/maxmind/libmaxminddb</a>
出現錯誤訊息 error while loading shared libraries: libmaxminddb.so.0: cannot open shared object file: No such file or directory
→ 安裝 libmaxminddb 後,要執行
$ sudo sh -c "echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf"
$ ldconfig
相關文章:
<a href="https://disp.cc/b/KnucklesNote/9SKl" target="_blank" rel="nofollow">[CentOS7] Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/gvK9" target="_blank" rel="nofollow">[RockyLinux9] 網頁伺服器 Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/8nyA" target="_blank" rel="nofollow">[Apache] log分析工具 AWStats - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/9TtD" target="_blank" rel="nofollow">[CentOS7] 設定 Apache mod_remoteip 修正 NodeBalancer 的IP - KnucklesNote板 - Disp BBS</a>
參考:
<a href="https://goaccess.io/man" target="_blank" rel="nofollow">GoAccess 官網文件</a>
<a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-goaccess-web-log-analyzer-on-ubuntu-20-04" target="_blank" rel="nofollow">https://www.digitalocean.com/communi...b-log-analyzer-on-ubuntu-20-04</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-11-24 22:48:03 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-26 04:09:14 (台灣)</span></div></pre>
Knuckles
[VSCode] 在 Visual Studio Code 使用 Git 版本控制
http://disp.cc/b/11-gD4Z
2023-11-12T21:16:12+08:00
2023-11-17T02:18:05+08:00
在 Visual Studio Code 使用 Git 版本控制,安裝環境 Windows 10
以下說明會以 VS Code 中文化的界面來操作 Git 的功能,再附上等同於 Git 的指令。
會提到這些 Git 的基本功能:
初始化存放庫 (Repository)
提交變更的檔案 (Commit)
複原上個提交 (Reset)
擱置變更 (Stash)
忽略檔案 (gitignore)
推送到 GitHub (push)
推送到自訂主機的存放庫
建立分支 (Branch)
最後介紹三個最熱門的 Git 延伸模組:
Git Graph、Git History、GitLens
# 安裝 Git for Windows
執行 VS Code 後,開啟一個專案資料夾,例如 vscode-test
點左邊的「原始檔控制」(Ctrl+Shift+G)
點「下載適用於 Windows 的 Git」會連到 https://git-scm.com/
點「Downloads」,點「Download for Windows」
點「Click here to download」
選項都照預設值點 Nex ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [VSCode] 在 Visual Studio Code 使用 Git 版本控制<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-11-12 Sun. 21:16:12</div><hr color="#008080" />
在 Visual Studio Code 使用 Git 版本控制,安裝環境 Windows 10
以下說明會以 VS Code 中文化的界面來操作 Git 的功能,再附上等同於 Git 的指令。
會提到這些 Git 的基本功能:
初始化存放庫 (Repository)
提交變更的檔案 (Commit)
複原上個提交 (Reset)
擱置變更 (Stash)
忽略檔案 (gitignore)
推送到 GitHub (push)
推送到自訂主機的存放庫
建立分支 (Branch)
最後介紹三個最熱門的 Git 延伸模組:
Git Graph、Git History、GitLens
<span style="color:#00F000"># 安裝 Git for Windows</span>
執行 VS Code 後,開啟一個專案資料夾,例如 vscode-test
點左邊的「原始檔控制」(Ctrl+Shift+G)
<div class="img" data-ori_w="430" data-ori_h="372" style="width:307px;height:266px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/a08ab9d.png" alt="[圖]" /></div>
點「下載適用於 Windows 的 Git」會連到 <a href="https://git-scm.com/" target="_blank" rel="nofollow">https://git-scm.com/</a>
點「Downloads」,點「Download for Windows」
<div class="img" data-ori_w="1351" data-ori_h="506" style="width:965px;height:361px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/a814c68.png" alt="[圖]" /></div>
點「Click here to download」
<div class="img" data-ori_w="924" data-ori_h="463" style="width:660px;height:331px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/f111c8f.png" alt="[圖]" /></div>
選項都照預設值點 Next 就好
安裝好 Git 後,在開始功能表裡可以看到有 Git CMD 和 Git Bash,其中 Git CMD 是給習慣用 Windows CMD 指令用的,Git Bash 是給習慣用 Linux Bash 指令用的
Git Bash 開啟的預設路徑是使用者的家目錄,想改成專案目錄的話
在開始功能表對 Git Bash 點右鍵/「更多」/「開啟檔案位置」
<div class="img" data-ori_w="724" data-ori_h="298" style="width:517px;height:213px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5cb2706.png" alt="[圖]" /></div>
在總案總管對 Git Bash 的捷徑點右鍵/「內容」
在捷徑頁的「目標」,刪除「--cd-to-home」
「開始位置」設為自己的專案目錄
<div class="img" data-ori_w="580" data-ori_h="375" style="width:414px;height:268px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/68e1361.png" alt="[圖]" /></div>
之後開啟 Git Bash 就會預設路徑為專案目錄了
執行 Git Bash,設定 Git 用戶的名稱和 E-mail
<code class="code-inline">$ git config --global user.name "Knuckles"</code>
<code class="code-inline">$ git config --global user.email "knuckles@xxxx.xxx"</code>
之後提交時會記錄提交者為這個使用者
檢查設定結果
<code class="code-inline">$ git config --list</code>
user.name=Knuckles
user.email=knuckles@xxxx.xxx
<span style="color:#00F000"># 初始化存放庫 (Repository)</span>
回到 VS Code,在原始檔控制選「將存放庫初始化」
等於在專案目錄裡執行指令 <code class="code-inline">$ git init</code>
<div class="img" data-ori_w="471" data-ori_h="458" style="width:336px;height:327px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/0c6b56e.png" alt="[圖]" /></div>
會在專案目錄中產生一個隱藏的 .git 資料夾,用來做 Git 存放庫 (Repository)
而專案目錄中其他的檔案與資料夾就叫工作目錄 (Working directory)
在原始檔控制頁,可以看到預設的分支名稱為 main
<div class="img" data-ori_w="457" data-ori_h="245" style="width:326px;height:175px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/f166339.png" alt="[圖]" /></div>
若要加入已經開啟的專案,點「⋯」/「複製」
等於指令 <code class="code-inline">$ git clone {存放庫的.git網址}</code>
<div class="img" data-ori_w="542" data-ori_h="292" style="width:387px;height:209px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/829d1f6.png" alt="[圖]" /></div>
接著輸入存放庫的 .git 連結,或是點「從GitHub複製」
<div class="img" data-ori_w="346" data-ori_h="104" style="width:247px;height:74px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/03ae398.png" alt="[圖]" /></div>
若是新開啟的專案的話,新增一個 test.php 檔
原始檔控制的「變更」裡會出現一個新增的檔案
<div class="img" data-ori_w="773" data-ori_h="328" style="width:552px;height:234px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/ded9341.png" alt="[圖]" /></div>
檔名會顯示為綠色,後面被標了個 U,
代表是新增的檔案,尚未被 Git 追蹤 (Untracked)
此時點「✔ 提交」的話會說沒有暫存變更(Staged changes)的檔案,是否要將所有變更的檔案都設為暫存變更並提交,點取消
<div class="img" data-ori_w="638" data-ori_h="197" style="width:456px;height:141px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/2363549.png" alt="[圖]" /></div>
<span style="color:#00F000"># 提交變更的檔案 (Commit)</span>
想要提交變更的檔案,可以點旁邊的加號「✚」
<div class="img" data-ori_w="366" data-ori_h="71" style="width:261px;height:51px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/badcea5.png" alt="[圖]" /></div>
或是對檔案按右鍵選「暫存變更」(Stage Changes)
<div class="img" data-ori_w="388" data-ori_h="400" style="width:277px;height:286px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/03f8591.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git add test.php</code>
可以看到 test.php 被移動到了「暫存的變更」區 (Staged Changes)
檔案後面的狀態也從 U (Untracked) 變成 A (Added)
<div class="img" data-ori_w="454" data-ori_h="100" style="width:324px;height:71px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/8fcbd0d.png" alt="[圖]" /></div>
後悔的話也可以點旁邊的減號「─」取消暫存變更
等於指令 <code class="code-inline">$ git reset test.php</code>
要查看檔案變更狀態與暫存的變更,也可以用指令 <code class="code-inline">$ git status</code>
接著要將暫存的變更提交,在上方的訊息輸入提交的說明,例如「建立新檔案」
點「✔ 提交」(Commit)
<div class="img" data-ori_w="454" data-ori_h="228" style="width:324px;height:163px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/b1f8439.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git commit -m "建立新檔案 test"</code>
若前面沒有設定 Git 使用者的話,這邊會跳出錯誤訊息
沒有輸入提交說明的話,會跳出編輯器,在開頭加上說明後存檔關閉即可
下面每行開頭為 # 的註解會被忽略,未輸入說明的話會取消提交
<div class="img" data-ori_w="829" data-ori_h="379" style="width:592px;height:271px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/03a2d79.png" alt="[圖]" /></div>
成功提交後,回到檔案總管查看 test.php,檔名變成白色了
點開左下角的「時間表」,可以看到有一個提交記錄了
<div class="img" data-ori_w="776" data-ori_h="371" style="width:554px;height:265px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/90b69f2.png" alt="[圖]" /></div>
提交後再修改程式,修改的地方在左邊會有不同的顏色標記
檔名會顯示為橘色,狀態會被標記為 M (Modified)
在原始檔控制會看到被加入到「變更」的檔案
<div class="img" data-ori_w="797" data-ori_h="320" style="width:569px;height:229px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/1e627cd.png" alt="[圖]" /></div>
點暫存變更後,輸入提交說明,點「✔ 提交」
<div class="img" data-ori_w="455" data-ori_h="228" style="width:325px;height:163px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/9535116.png" alt="[圖]" /></div>
在時間表可以看到已記錄了第二個提交
<div class="img" data-ori_w="390" data-ori_h="150" style="width:279px;height:107px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/65c2d2c.png" alt="[圖]" /></div>
要查看提交記錄,也可以用指令 <code class="code-inline">$ git log</code>
如果想要再補充一些更新,但不想再新增一個提交,或是只是想更改提交說明的話
提交時可改用下拉選單的「認可(修改)」英文是 Commit (Amend)
或是點存放庫選單的「⋯」/「提交」/「認可(修改)」
<div class="img" data-ori_w="932" data-ori_h="416" style="width:666px;height:297px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/6d466ee.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git commit --amend</code>
會將此次修改併入上一次的提交
<span style="color:#00F000"># 復原上個提交 (Reset)</span>
要是想直接取消上一次的提交,點存放庫右邊的選單「⋯」/「提交」/「複原上個提交」
英文是 Commit / Undo Last Commit
<div class="img" data-ori_w="919" data-ori_h="757" style="width:656px;height:541px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/71f47fe.png" alt="[圖]" /></div>
要回到前幾次的提交就再復原幾次即可
存放庫選單的功能也可以用 Ctrl+Shift+P 輸入 git commit 後選擇想要的指令
<div class="img" data-ori_w="524" data-ori_h="623" style="width:374px;height:445px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/2169560.png" alt="[圖]" /></div>
例如新增了一行程式並提交,用來測試看看復原上個提交
<div class="img" data-ori_w="932" data-ori_h="237" style="width:666px;height:169px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/6309cb4.png" alt="[圖]" /></div>
執行「復原上個提交」後,可以看到剛剛的提交取消了,但新增的程式還在,程式被放回到「暫存的變更」區
<div class="img" data-ori_w="936" data-ori_h="342" style="width:669px;height:244px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/ead5ca6.png" alt="[圖]" /></div>
點存放庫選單「⋯」/「顯示 Git 輸出」,可以看到實際上是執行了
<code class="code-inline">$ git reset --soft HEAD~</code>
使用 reset 重置目前的版本到指定的提交記錄
其中 --soft 代表不回復工作目錄的檔案,也不回復暫存的變更
HEAD~ 代表目前分支最新版本的上一個提交記錄
若是「回復上個提交」後再點一下「取消所有的暫存變更」
就相當於執行了 <code class="code-inline">$ git reset HEAD~</code>
<div class="img" data-ori_w="704" data-ori_h="118" style="width:503px;height:84px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/b0ab0c4.png" alt="[圖]" /></div>
若是「回復上個提交」、「取消所有的暫存變更」,再點「捨棄所有變更」
就相當於執行了 <code class="code-inline">$ git reset --hard HEAD~</code>
<div class="img" data-ori_w="670" data-ori_h="115" style="width:479px;height:82px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/414610e.png" alt="[圖]" /></div>
代表完全回復到上次提交時的狀態,工作目錄變更的程式碼都會回復
<span style="color:#00F000"># 擱置變更 (Stash)</span>
如果想要把目前已變更的檔案全部先存起來放著不處理,可以使用擱置變更(Stash)
例如在程式加了一行註解,所以有一個變更的檔案
<div class="img" data-ori_w="932" data-ori_h="323" style="width:666px;height:231px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/39f29f6.png" alt="[圖]" /></div>
點選存放庫選單「⋯」/「隱藏」/「擱置變更(Stash)」,英文是 Stash / Stash
<div class="img" data-ori_w="1044" data-ori_h="881" style="width:746px;height:629px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/288e407.png" alt="[圖]" /></div>
接著輸入 Stash 的說明,例如「加上註解」
<div class="img" data-ori_w="510" data-ori_h="101" style="width:364px;height:72px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/d171282.png" alt="[圖]" /></div>
等於指令 $ git stash push -m 加上註解
會看到 test.php 沒有在變更的檔案裡了,檔案裡加上的註解也消失了
<div class="img" data-ori_w="931" data-ori_h="273" style="width:665px;height:195px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/87123d0.png" alt="[圖]" /></div>
想要把未追蹤(標記 U )的檔案也隱藏的話,要選擇 「⋯」/「隱藏」/「擱置變更(包含未被追蹤的檔案)」,但要注意這樣檔案會從工作目錄移除
想要回復隱藏的變更,選「⋯」/「隱藏」/「套用擱置… (Apply Stash)」,
選擇要套用的 Stash 即可
<div class="img" data-ori_w="364" data-ori_h="140" style="width:260px;height:100px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/2b026e9.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git stash apply stash@{0}</code>
要套用並刪除這個 Stash 的話,選「取回擱置…(Pop Stash)」
<code class="code-inline">$ git stash pop stash@{0}</code>
# 忽略檔案 (gitignore)
若有檔案不想加入 Git 追蹤,例如專案的設定值、含有隱密資料的檔案
可以按右鍵選「新增到 .gitignore」
<div class="img" data-ori_w="457" data-ori_h="552" style="width:326px;height:394px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/f337007.png" alt="[圖]" /></div>
會在專案目錄新增一個 .gitignore 檔案,並加入要忽略的檔案
若要忽略整個資料夾的內容,例如 .vscode,要修改 .gitignore 檔案,
加入 .vscode/*
<span style="color:#00F000"># 推送到 GitHub (push)</span>
要讓程式可以與其他人共同開放的話,可以使用發佈(Public)上傳到 GitHub
當所有變更的檔案都提交後,提交按鈕就會變成「發佈 Branch」
或是在原始檔控制的分支 main 旁點發佈的圖示
<div class="img" data-ori_w="466" data-ori_h="253" style="width:333px;height:181px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/2aede32.png" alt="[圖]" /></div>
出現需要 GitHub 登入權限的話,輸入 GitHub 帳號密碼登入
輸入要在 GitHub 上建立的存放庫名稱,選擇要私人還是公開
現在免付費帳號也可以使用私人存放庫了
<div class="img" data-ori_w="608" data-ori_h="136" style="width:434px;height:97px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/32b4575.png" alt="[圖]" /></div>
選好後就會自動在 GitHub 建好新的存放庫,不用自己登入建立
然後會推送(Push)本機分支 main 至遠端分支 origin/main,並設定了追蹤,
等於以下指令
<code class="code-inline">$ git remote add origin <a href="https://github.com/KnucklesHuang/vscode-test.git" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/vscode-test.git</a></code>
新增一個遠端分支,其中 orgin 為預設的遠端分支名稱
<code class="code-inline">$ git push -u origin main</code>
推送分支 main 到遠端 origin
其中 -u 等於 --set-upstream 設定分支開始追蹤指定的遠端分支
發佈成功後,分支名稱旁的圖示就會變成「同步處理」
<div class="img" data-ori_w="464" data-ori_h="254" style="width:331px;height:181px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/fd2f472.png" alt="[圖]" /></div>
此時已設定分支 main 要追蹤遠端分支 orgin/main
可以直接使用「推送」「提取」,不需要選擇遠端
登入 GitHub 可以看到上傳好的程式
<div class="img" data-ori_w="909" data-ori_h="530" style="width:649px;height:379px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/060c4ed.png" alt="[圖]" /></div>
點「Add a README」在 GitHub 新增一個說明檔
編輯完說明後,點「Commit changes」
<div class="img" data-ori_w="1030" data-ori_h="240" style="width:736px;height:171px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/9d57ed4.png" alt="[圖]" /></div>
在 Commit message 輸入提交的說明後點「Commit changes」
<div class="img" data-ori_w="654" data-ori_h="671" style="width:467px;height:479px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/8054569.png" alt="[圖]" /></div>
這樣會在 GitHub 這邊也建立了一次提交
要把 GitHub 存放庫的提交提取(Pull)回來並合併至本機的分支 main
可以點分支 main 旁的同步處理圖示,或是選擇「⋯」/「提取、推送」/「同步處理」
同步處理就是先提取再推送,等於指令 <code class="code-inline">$ git pull</code> 接著 <code class="code-inline">$ git push</code>
<div class="img" data-ori_w="464" data-ori_h="254" style="width:331px;height:181px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/fd2f472.png" alt="[圖]" /></div>
同步處理成功的話,在檔案總管就可以看到新增的 README.md 檔
<div class="img" data-ori_w="806" data-ori_h="345" style="width:576px;height:246px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/eebc2e5.png" alt="[圖]" /></div>
若提取時出現錯誤訊息:There is no tracking information for the current branch.
Please specify which branch you want to merge with.
代表目前分支沒有追蹤的遠端分支,改為使用「提取自...」就可以指定遠端分支,再使用發佈分支即可追蹤遠端分支
注意若已將提交推送出去,就不能再用 Commit (Amend) 修改已推送的提交
<span style="color:#00F000"># 推送到自訂主機的存放庫</span>
如果不想使用 GitHub 當遠端存放庫的話,也可以使用自己的主機
使用 Git Bash 切換到存放專案的目錄,例如
<code class="code-inline">$ cd /e/wamp/www</code>
產生一個給遠端主機用的存放庫 vscode-test.git
<code class="code-inline">$ git clone --bare vscode-test vscode-test.git</code>
然後將 vscode-test.git 資料夾傳送至遠端主機
例如放在 my-host.com 的 /home/knuckles/git/
在 VS Code 新增一個遠端存放庫
在存放庫選單,選擇「⋯」/「遠端」/「新增遠端存放庫」
或是 Ctrl+Shift+P,搜尋「add remote」
<div class="img" data-ori_w="441" data-ori_h="131" style="width:315px;height:94px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/9a11098.png" alt="[圖]" /></div>
輸入遠端存放庫的 URL 例如 ssh://knuckles@my-host.com:1234/home/knuckles/git/vscode-test.git
其中 knuckles 為登入的帳號,my-host為自己的主機位址,1234 為 ssh 的 port
<div class="img" data-ori_w="1059" data-ori_h="194" style="width:756px;height:139px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/d7b5694.png" alt="[圖]" /></div>
為這個新增的遠端存放庫取個名稱,例如「my-host」
<div class="img" data-ori_w="491" data-ori_h="110" style="width:351px;height:79px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/8df6723.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git remote add my-host {遠端存放庫的URL}</code>
要把本機的分支 main 推送(push)到遠端存放庫
可以點存放庫選單「⋯」/「提取、推送」/「推送至…」
或是用 Ctrl+Shift+P,搜尋「push to」然後選取要推送到遠端 my-host
<div class="img" data-ori_w="453" data-ori_h="300" style="width:324px;height:214px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/7da495a.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git push my-host main</code>
要把遠端存放庫的提交提取(Pull)回來並合併至本機的分支 main
可以點存放庫選單「⋯」/「提取、推送」/「從…提取」
或是用 Ctrl+Shift+P,搜尋「pull from」然後選取要從遠端分支 my-host/main 提取
<div class="img" data-ori_w="423" data-ori_h="247" style="width:302px;height:176px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/505d698.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git pull my-host main</code>
要讓本機分支改為追蹤遠端分支 my-host/main 的話,可以使用指令
<code class="code-inline">$ git branch -u my-host/main</code>
-u 等於 --set-upstream 設定分支開始追蹤指定的遠端分支
更改追蹤後,就可以直接點提取、推送或同步處理了
<span style="color:#00F000"># 建立分支 (Branch)</span>
想要開發一個新功能,但又不想在開發過程影響到本來的程式,這時就可以開一個新的分支,等功能寫好後再合併回原本的分支
在存放庫選單,點「⋯」/「分支」/「建立分支...」
<div class="img" data-ori_w="548" data-ori_h="316" style="width:391px;height:226px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/cdf1def.png" alt="[圖]" /></div>
輸入分支名稱
<div class="img" data-ori_w="479" data-ori_h="102" style="width:342px;height:73px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/bb8f7cc.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git checkout -b test1</code>
其中 checkout 是移動到分支,加上 -b 代表建立分支後移動過去,也可以使用
<code class="code-inline">$ git branch test1</code>
<code class="code-inline">$ git checkout test1</code>
先建立好分支 test1,再移動過去
在存放庫可以看到目前分支變成 test1 了,在程式加上修改後提交
<div class="img" data-ori_w="945" data-ori_h="313" style="width:675px;height:224px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/4f24803.png" alt="[圖]" /></div>
查看目前在哪個分支,也可以用指令 <code class="code-inline">$ git branch</code>
想回到分支 main 時,點存放庫的分支名稱,或點選單「⋯」/「簽出至…」
<div class="img" data-ori_w="701" data-ori_h="216" style="width:501px;height:154px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/46ca2ab.png" alt="[圖]" /></div>
選擇分支 main
<div class="img" data-ori_w="382" data-ori_h="246" style="width:273px;height:176px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/9811734.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git checkout main</code>
回到分支 main 後,可以發現在分支新增的程式不見了,
我們再這邊新增其他的程式後提交
<div class="img" data-ori_w="946" data-ori_h="304" style="width:676px;height:217px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/7094fc7.png" alt="[圖]" /></div>
此時想要將分支 test1 的修改合併過來的話,
在分支 main 的存放庫選單點「⋯」/「分支」/「合併分支…」
<div class="img" data-ori_w="546" data-ori_h="167" style="width:390px;height:119px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/13e0956.png" alt="[圖]" /></div>
選擇要合併的分支 test1
<div class="img" data-ori_w="262" data-ori_h="164" style="width:187px;height:117px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/d2d30f7.png" alt="[圖]" /></div>
等於指令 <code class="code-inline">$ git merge test1</code>
此時顯示檔案 test.php 發生了1個衝突,檔案狀態被標記為「1, !」
可選擇要用分支 main 的版本,還是分支 test1 的版本,
或是兩個都要的話選擇「接受兩者變更」
<div class="img" data-ori_w="1034" data-ori_h="374" style="width:739px;height:267px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/c5d66f6.png" alt="[圖]" /></div>
接受兩者變更後,分別在兩個分支寫的程式都加了上來,檔案狀態為 !
在原始檔控制的「合併變更」區點 ✚ 暫存變更
<div class="img" data-ori_w="947" data-ori_h="348" style="width:676px;height:249px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/57b04a5.png" alt="[圖]" /></div>
提交的說明已經自動寫好了,點「✔ 提交」
<div class="img" data-ori_w="953" data-ori_h="342" style="width:681px;height:244px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/099c10e.png" alt="[圖]" /></div>
就會將分支 test1 的變更,合併至分支 main 了
<span style="color:#00F000"># 安裝 Git Graph 模組</span>
<div class="img" data-ori_w="1474" data-ori_h="597" style="width:1053px;height:426px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/795107d.png" alt="[圖]" /></div>
在原始檔控制存放庫,點 Git Graph 圖示,可顯示圖型化的分支記錄
<div class="img" data-ori_w="1287" data-ori_h="391" style="width:919px;height:279px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/db0ed83.png" alt="[圖]" /></div>
點右邊的齒輪圖示 Repository Settings,可以設定 Git 使用者的 Name 和 Email
也可以設定遠端存放庫
<div class="img" data-ori_w="790" data-ori_h="732" style="width:564px;height:523px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5772362.png" alt="[圖]" /></div>
<span style="color:#00F000"># 安裝 Git History 模組</span>
<div class="img" data-ori_w="1289" data-ori_h="500" style="width:921px;height:357px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/b989ef2.png" alt="[圖]" /></div>
檔案的右上角按鈕,或是檔案的右鍵選單,
會多一個「Git: View File History (Alt+H)」,會列出檔案提交記錄
<div class="img" data-ori_w="1469" data-ori_h="461" style="width:1049px;height:329px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/bc3dbf9.png" alt="[圖]" /></div>
點原始檔存放庫旁邊的圖示,也可以開啟目前分支的提交記錄
<div class="img" data-ori_w="1390" data-ori_h="629" style="width:993px;height:449px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/691cfbe.png" alt="[圖]" /></div>
<span style="color:#00F000"># 安裝 GitLens 模組</span>
<div class="img" data-ori_w="1468" data-ori_h="674" style="width:1049px;height:481px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/aaf0654.png" alt="[圖]" /></div>
在原始檔控制加入更多的資料檢視
<div class="img" data-ori_w="622" data-ori_h="673" style="width:444px;height:481px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/0a142cf.png" alt="[圖]" /></div>
<div class="img" data-ori_w="444" data-ori_h="285" style="width:317px;height:204px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/10a3479.png" alt="[圖]" /></div>
想自訂 Git 使用者圖示的話,可以到 <a href="https://gravatar.com/" target="_blank" rel="nofollow">https://gravatar.com/</a> 用 E-mail 登入設定
檔案的上方會顯示編輯的使用者,游標所在的行會顯示提交資訊 (Inline Blame)
<div class="img" data-ori_w="948" data-ori_h="293" style="width:677px;height:209px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/cb49639.png" alt="[圖]" /></div>
點編輯器上面向左的圖示,或是對檔案按右鍵選「Open Changes」/「Open Changes with Previous Revision」
<div class="img" data-ori_w="1115" data-ori_h="270" style="width:796px;height:193px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/dd79add.png" alt="[圖]" /></div>
可快速查看每次提交更改的地方,再點一次圖示
<div class="img" data-ori_w="864" data-ori_h="252" style="width:617px;height:180px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/f618271.png" alt="[圖]" /></div>
可切換到更上一次更改的地方
<div class="img" data-ori_w="862" data-ori_h="197" style="width:616px;height:141px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/088b018.png" alt="[圖]" /></div>
沒看到圖示的話,因為最多只能顯示8個,可以按右鍵選擇要使用的圖示
<div class="img" data-ori_w="403" data-ori_h="594" style="width:288px;height:424px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/71c2e43.png" alt="[圖]" /></div>
點存放庫旁的圖示可顯示分支的提交記錄
<div class="img" data-ori_w="1497" data-ori_h="508" style="width:1069px;height:363px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/76f6e11.png" alt="[圖]" /></div>
預設是顯示在下方的面版,想改成顯示在編輯器的話,在設定搜尋
「gitlens graph layout」,把「panel」改成「editor」
<div class="img" data-ori_w="972" data-ori_h="839" style="width:694px;height:599px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/df20e8c.png" alt="[圖]" /></div>
相關文章:
<a href="https://disp.cc/b/KnucklesNote/gBWu" target="_blank" rel="nofollow">[VSCode] Visual Studio Code 安裝與設定 - KnucklesNote板 - Disp BBS</a>
參考:
VSCode 官網文件 <a href="https://code.visualstudio.com/docs/sourcecontrol/overview" target="_blank" rel="nofollow">https://code.visualstudio.com/docs/sourcecontrol/overview</a>
<a href="https://hackmd.io/@howhow/git_with_vscode" target="_blank" rel="nofollow">https://hackmd.io/@howhow/git_with_vscode</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-11-12 21:16:12 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-17 02:18:05 (台灣)</span></div></pre>
Knuckles
[VSCode] Visual Studio Code 安裝與設定
http://disp.cc/b/11-gBWu
2023-11-06T02:25:56+08:00
2023-12-09T02:30:52+08:00
安裝環境 Windows 10
在官網下載安裝 https://code.visualstudio.com/
# 安裝 Extensions 延伸模組
安裝中文化模組
點左邊 Extensions 延伸模組,搜尋 Chinese 安裝繁體中文模組
點 Ctrl+Shift+P 搜尋 display,
選 Configure Display Language 可切換顯示顯示語言
安裝 PHP All-in-One 模組
# 儲存工作區
使用「檔案」/「開啟資料夾」打開一個專案的資料夾
例如開啟一個新專案資料夾「vscode-test」
新增一個檔案看看
使用「檔案」/「另存工作區」,將工作區設定檔存在專案目錄下的「.vscode/」
可以看到專案資料夾被放到了工作區
用「檔案」/「將資料夾新增至工作區」,可以把其他專案的資料夾也放進來
之後這個工作區的設定、檔案的開啟狀態、要啟用的模組就會被儲存下來
使用「檔案」/「從檔案開啟工作區」就可以切換不同的工作狀態
或是安裝「Project Manager」模組
安裝後左邊會多一個 Project Manager 的圖示
點「Project M ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [VSCode] Visual Studio Code 安裝與設定<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-11-06 Mon. 02:25:56</div><hr color="#008080" />
安裝環境 Windows 10
在官網下載安裝 <a href="https://code.visualstudio.com/" target="_blank" rel="nofollow">https://code.visualstudio.com/</a>
<div class="img" data-ori_w="1353" data-ori_h="813" style="width:966px;height:581px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/ff3824a.png" alt="[圖]" /></div>
# 安裝 Extensions 延伸模組
安裝中文化模組
點左邊 Extensions 延伸模組,搜尋 Chinese 安裝繁體中文模組
<div class="img" data-ori_w="1378" data-ori_h="752" style="width:984px;height:537px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/2ff966b.png" alt="[圖]" /></div>
點 Ctrl+Shift+P 搜尋 display,
選 Configure Display Language 可切換顯示顯示語言
<div class="img" data-ori_w="1194" data-ori_h="266" style="width:853px;height:190px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/0628927.png" alt="[圖]" /></div>
安裝 PHP All-in-One 模組
<div class="img" data-ori_w="1429" data-ori_h="806" style="width:1021px;height:576px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5055a35.png" alt="[圖]" /></div>
# 儲存工作區
使用「檔案」/「開啟資料夾」打開一個專案的資料夾
例如開啟一個新專案資料夾「vscode-test」
<div class="img" data-ori_w="653" data-ori_h="148" style="width:466px;height:106px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/ffacb7d.png" alt="[圖]" /></div>
新增一個檔案看看
<div class="img" data-ori_w="785" data-ori_h="205" style="width:561px;height:146px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/e066e43.png" alt="[圖]" /></div>
使用「檔案」/「另存工作區」,將工作區設定檔存在專案目錄下的「.vscode/」
<div class="img" data-ori_w="936" data-ori_h="302" style="width:669px;height:216px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/6d546a2.png" alt="[圖]" /></div>
可以看到專案資料夾被放到了工作區
用「檔案」/「將資料夾新增至工作區」,可以把其他專案的資料夾也放進來
之後這個工作區的設定、檔案的開啟狀態、要啟用的模組就會被儲存下來
使用「檔案」/「從檔案開啟工作區」就可以切換不同的工作狀態
或是安裝「Project Manager」模組
<div class="img" data-ori_w="1308" data-ori_h="508" style="width:934px;height:363px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/3b700dd.png" alt="[圖]" /></div>
安裝後左邊會多一個 Project Manager 的圖示
<div class="img" data-ori_w="432" data-ori_h="478" style="width:309px;height:341px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/661088b.png" alt="[圖]" /></div>
點「Project Manager: Save Project」後,
輸入專案名稱,例如「vscode-test」
<div class="img" data-ori_w="435" data-ori_h="469" style="width:311px;height:335px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/d38d3ba.png" alt="[圖]" /></div>
用這個就不用儲存工作區,且可以方便的切換不同的專案了
要改成繁體中文,可以搜尋模組的使用者設定檔 C:\Users\{username}\.vscode\extensions\alefragnani.project-manager-12.7.0\package.nls.zh-cn.json
將內容的簡體中文改成繁體中文後,另存成 package.nls.zh-tw.json,重啟 VS Code 即可
# 功能設定
左下角可登入帳號,登入後設定值可以同步至其他裝置
<div class="img" data-ori_w="423" data-ori_h="421" style="width:302px;height:301px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/cb930a5.png" alt="[圖]" /></div>
點設定,或快速鍵 Ctrl+,
<div class="img" data-ori_w="1122" data-ori_h="707" style="width:801px;height:505px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/c327516.png" alt="[圖]" /></div>
如果要設定 php 執行檔路徑與 php 版本的話
在設定搜尋 php: executable
只要設定目前這個工作區的話選「工作區」
<div class="img" data-ori_w="1247" data-ori_h="572" style="width:891px;height:409px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/11d8015.png" alt="[圖]" /></div>
點「在 settings.json 內編輯」加上 php 的執行路徑,例如
"php.validate.executablePath": "E:\\wamp\\bin\\php\\php5.6.40\\php.exe"
然後在 PHP: Version 輸入 PHP 的版本
或是打開一個 .php 檔後,點右下角顯示的 PHP 版本,就可以選擇
<div class="img" data-ori_w="780" data-ori_h="459" style="width:557px;height:328px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5f482bb.png" alt="[圖]" /></div>
設定文字編輯器自動換行,搜尋 word wrap
設定為 wordWrapColumn,超過80字元的時候換行
<div class="img" data-ori_w="1032" data-ori_h="435" style="width:737px;height:311px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/58c6e8c.png" alt="[圖]" /></div>
或是使用 Ctrl+Shift+P 輸入 word wrap 切換設定
<div class="img" data-ori_w="842" data-ori_h="127" style="width:601px;height:91px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/f514e37.png" alt="[圖]" /></div>
搜尋 Editor: Links
可關閉編輯器偵測連結
搜尋 Editor: Format On Paste
貼上程式時自動格式化
搜尋 Workbench › Editor: Enable Preview
<div class="img" data-ori_w="713" data-ori_h="297" style="width:509px;height:212px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/10bd713.png" alt="[圖]" /></div>
點選檔案後會顯示內容,上面紅框的檔名為斜體代表為預覽模式
再點選其他檔案就會換成顯示其他檔案的內容,以避免開啟太多分頁
要點兩下開啟或編輯內容才會固定住
搜尋 Sticky Scroll 可開啟自動將類別或函數開頭固定在上方
<div class="img" data-ori_w="918" data-ori_h="465" style="width:656px;height:332px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/3be8767.png" alt="[圖]" /></div>
搜尋 Workbench Editor: Wrap Tabs
可設定分頁很多時使用多行顯示
<div class="img" data-ori_w="1152" data-ori_h="139" style="width:823px;height:99px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/1d394fb.png" alt="[圖]" /></div>
程式碼折疊
在多行註解或函數的左邊,滑鼠移過去會出現向下箭頭,點一下即可折疊
<div class="img" data-ori_w="727" data-ori_h="368" style="width:519px;height:263px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/c6f0ac1.png" alt="[圖]" /></div>
或使用自訂折疊標記,例如 PHP 可以用
#region 從這邊開始
……
#endregion
# 快速鍵設定
快速鍵對照表參考:
<a href="https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf" target="_blank" rel="nofollow">https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf</a>
<a href="https://ithelp.ithome.com.tw/articles/10237385" target="_blank" rel="nofollow">https://ithelp.ithome.com.tw/articles/10237385</a>
<a href="https://summer10920.github.io/2020/10-23/article-vscode/" target="_blank" rel="nofollow">https://summer10920.github.io/2020/10-23/article-vscode/</a>
點左下角的齒輪/鍵盤快速鍵 Ctrl+K Ctrl+S
<div class="img" data-ori_w="426" data-ori_h="423" style="width:304px;height:302px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/6b5296e.png" alt="[圖]" /></div>
輸入命令描述或快速鍵可搜尋
<div class="img" data-ori_w="1130" data-ori_h="587" style="width:807px;height:419px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/58e51ab.png" alt="[圖]" /></div>
點「變更按鍵...」更改快速鍵
點「新增按鍵...」可新增另一個快速鍵
點「移除按鍵...」移除這個快速鍵
Ctrl+R 開啟其他之前開過的專案
Ctrl+P 開啟專案中的檔案
瀏覽視窗
Ctrl+P 輸入 @ 可選擇目前檔案中的符號(function 或 class)
Ctrl+G 前往輸入的行數
Alt+左/右 前一個或後一個游標瀏覽處
Ctrl+1,2,3 切換到第幾個視窗群組
Ctrl+Tab 在視窗群組中切換視窗
編輯器
Ctrl+Alt+上/下 移動這一行到上一行或下一行 (預設是 Alt+上/下)
移除Ctrl+Alt+上/下原本的功能(在上/下一行加入游標)
Shift+Alt+上/下 複製這一行到上一行或下一行
Shift+Enter 下方新增一行 (預設是Ctrl+Enter)
editor.action.inertLineAfter
Shift+Delete 刪除這一行
Ctrl+X, Ctrl+C 剪下/複製在未選取字串時,會整行剪下/複製
Ctrl+D 選取目前游標所在的字串,再按一次就會同時選取下一個相同的字串,此時可同時修改每個選取的字串
使用 F7 / shift+F7 可移動到下一個/上一個變數的位置
editor.action.wordHighlight.next/prev
新增另一個快速鍵為 Alt+下/上,移除原本的功能(移動這行到下/上一行)
對變數按 F2 可更新所有位置的名稱
如果是全域變數的話,輸入新的名稱後按 Shift+Enter 可預覽變更的範圍
Ctrl+F2 同時選取所有位置
游標所在的字串或變數可使用 ctrl+f 搜尋後,Enter / shift+Enter 移動到下一個/上一個
# 設定 SFTP 上傳
安裝 SFTP 模組
<div class="img" data-ori_w="1384" data-ori_h="528" style="width:989px;height:377px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5cca9e2.png" alt="[圖]" /></div>
開啟專案資料夾
Ctrl+Shift+p 輸入「SFTP: config」
會在專案資料夾下新增 /.vscode/sftp.json
<div class="highlight"><span></span><span class="p">{</span>
<span class="w"> </span><span class="s2">"name"</span><span class="o">:</span><span class="w"> </span><span class="s2">"My Host"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"host"</span><span class="o">:</span><span class="w"> </span><span class="s2">"my-host.com"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"protocol"</span><span class="o">:</span><span class="w"> </span><span class="s2">"sftp"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"port"</span><span class="o">:</span><span class="w"> </span><span class="mf">22</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"username"</span><span class="o">:</span><span class="w"> </span><span class="s2">"username"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"remotePath"</span><span class="o">:</span><span class="w"> </span><span class="s2">"/var/www/vscode-test"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"privateKeyPath"</span><span class="o">:</span><span class="w"> </span><span class="s2">"C:\\Users\\username\\.ssh\\id_rsa"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"uploadOnSave"</span><span class="o">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"useTempFile"</span><span class="o">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"openSsh"</span><span class="o">:</span><span class="w"> </span><span class="kc">false</span>
<span class="p">}</span>
</div>
host 輸入要上傳的主機位址
username 輸入用ssh登入主機的帳號
privateKeyPath 輸入ssh登入私鑰的檔案路徑
注意路徑的反斜線 \ 要用 \\ 或改成斜線 /
存檔後,左邊會多一個 SFTP 的圖示,點選後可瀏覽遠端的檔案
對檔案或資料夾點右鍵可上傳或下載
<div class="img" data-ori_w="487" data-ori_h="474" style="width:348px;height:339px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/d409c49.png" alt="[圖]" /></div>
在檔案總管點右鍵也會出現上傳或下載的選項,點「Upload File」
<div class="img" data-ori_w="264" data-ori_h="127" style="width:189px;height:91px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/4d2895d.png" alt="[圖]" /></div>
下面紅框 SFTP 的地方會顯示正在上傳,上傳成功後顯示done,
點一下可顯示上傳記錄
<div class="img" data-ori_w="1187" data-ori_h="183" style="width:848px;height:131px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/87c3595.png" alt="[圖]" /></div>
有使用原始檔控制的話,在檔案變更區會多一個按鈕,可一次上傳所有變更的檔案
<div class="img" data-ori_w="922" data-ori_h="329" style="width:659px;height:235px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/51165f1.png" alt="[圖]" /></div>
也可以使用快速鍵 Ctrl+Alt+U
另外新增快速鍵 Ctrl+Alt+S,可上傳目前這個檔案
<div class="img" data-ori_w="869" data-ori_h="216" style="width:621px;height:154px"><img style="max-width:100%;" data-ratio="1.4" src="https://i4.disp.cc/u/23/5cd1e17.png" alt="[圖]" /></div>
# 安裝 Material 圖示佈景
在延伸模組搜尋 icon 安裝最多人用的 Material Icon Theme
<div class="img" data-ori_w="1439" data-ori_h="302" style="width:1028px;height:216px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/qYwTgfxn3eFt1fybvOg_zrYwVefd4haVSd-WjsOzS1Vatxtu-tD-n99VPIr7d1pYYVuY0kyVy_dRv6YPMYSi2K8rZKOrZm8jwUTeIDXGl4H0pzJltjst0Rv9zdgbG_mM8t6wxutuecPrH4X5hk6MbB4" alt="[圖]" /></div>
安裝後可以看到各種資料夾和檔案都被換成了各種圖示
<div class="img" data-ori_w="228" data-ori_h="463" style="width:163px;height:331px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/zvKVsWBAd-kkVSRGMhoX5IJVQnAMo9qbDo0gy2M-UcM2e3kK4lG4kzQZ6KGm_v0-CQfAT99YDe6PjYQHZiV-iUOOg-eAHBbfSWz6pJbCpOyi-w1rM8cWJp2gTBh2h0wOn1jv1_OTnCJbZAgcqLvWjo4" alt="[圖]" /></div> <div class="img" data-ori_w="276" data-ori_h="523" style="width:197px;height:374px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/eXEzJXmqI_66XmE9RcosZI1yZ4quV6-2fRwq1AzgINiNEgwpJ2LsAVnQ51rnpJje_eQDxxvJCMb2ntp4qePrq4iO_R9zXHe3kXjYnAetCUFpqh4DDGBQhIQG948a_y8MYwwMuD8izT0G6IgSuyesqCQ" alt="[圖]" /></div> <div class="img" data-ori_w="309" data-ori_h="589" style="width:221px;height:421px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/ugomlSdsvyhBDmkefDUnq-WtxoZ-7FpOklQPpyGCyH3oK_aYEJ64IbAJZDYueLnX34Jdx3U1mdOqt84f_IdyU8KnpXT044IcKY8ZjRfc0h0OYZ63wny4-EIJWuLSDXXyuqdEWnPwbB81Hw-bHWXvDvk" alt="[圖]" /></div>
在設定搜尋 material 可以更改成自己喜歡的檔案或資料夾圖示
<div class="img" data-ori_w="823" data-ori_h="690" style="width:588px;height:493px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/2f0saZOq_07yO4YU4W-nVozB0pfb8Wl3qMDFUQfXofgBxqyZ_V6ce2nLysA-2BS8u-bPk5VNBN7jeACrYCKeIsT5YUL6twEgv37mg6F9XCo8yMO3EumBqBsrtps-wgPubKD_0qviMCP5PdSqliC1lEc" alt="[圖]" /></div>
例如將 php 圖示改成小象,storage 資料夾改成上傳
"material-icon-theme.files.associations": {
"*.php": "Php_elephant"
},
"material-icon-theme.folders.associations": {
"storage": "Uploads"
},
<div class="img" data-ori_w="244" data-ori_h="88" style="width:174px;height:63px"><img style="max-width:100%;" data-ratio="1.4" src="https://lh7-us.googleusercontent.com/cn4npUKIwzigGdphohe8SbRBoDAAzUIdb0i7Jl0GJ6tz1uhcvi0ROxff1wuO5exZGNkBGj7mVBKe81AnoMzKeMROP6xadoWNy5pcuMjG5tCmnekbj4lmN3-sNQbRNZyAYytB879zg2T6UgJpzIZxG2c" alt="[圖]" /></div>
圖示名稱在這邊查詢 <a href="https://github.com/PKief/vscode-material-icon-theme" target="_blank" rel="nofollow">https://github.com/PKief/vscode-material-icon-theme</a>
相關文章:
<a href="https://disp.cc/b/KnucklesNote/gD4Z" target="_blank" rel="nofollow">[VSCode] 在 Visual Studio Code 使用 Git 版本控制 - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-11-06 02:25:56 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-12-09 02:30:52 (台灣)</span></div></pre>
Knuckles
[軟體] Cmder 在Windows下使用像Linux的指令模式
http://disp.cc/b/11-gz5A
2023-10-17T16:28:55+08:00
2023-11-02T01:27:17+08:00
在 Windows 下使用指令操作時
要使用不太好用的命令提示字元
如果想要有像 Linux 一樣好用的指令模式
可以安裝 Cmder
在官網 https://cmder.app/
點「Download Full」,下載後解壓縮,執行 cmder.exe 即可
# 將 λ 改成 $
Cmder 預設提示字元 λ 會導致中文排版錯亂
修改 cmder\config\cmder_prompt_config.lua
22行的 prompt_lambSymbol = "λ"
將 λ 改為 $
重啟 cmder
# 以 win + R 開啟 Cmder
右鍵 本機,內容 → 進階系統設定 → 進階 → 環境變數 → 編輯 系統變數底下的 Path
加上 Cmder 的目錄
之後只要使用 win+r,輸入 cmder 即可執行
要執行並切換到指定目錄的話,可輸入 cmder {路徑}
例如: cmder e:/wamp/www
# 新增 Cmder 到右鍵選單
用管理員權限開啟命令提示字元,輸入
Cmder.exe /REGISTER ALL
# 修改 Alias 加上常用指令 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [軟體] Cmder 在Windows下使用像Linux的指令模式<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-10-17 Tue. 16:28:55</div><hr color="#008080" />
在 Windows 下使用指令操作時
要使用不太好用的命令提示字元
如果想要有像 Linux 一樣好用的指令模式
可以安裝 Cmder
<div class="img" data-ori_w="1431" data-ori_h="1089" style="width:716px;height:545px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/e5ac6b7.png" alt="[圖]" /></div>
在官網 <a href="https://cmder.app/" target="_blank" rel="nofollow">https://cmder.app/</a>
點「Download Full」,下載後解壓縮,執行 cmder.exe 即可
# 將 λ 改成 $
Cmder 預設提示字元 λ 會導致中文排版錯亂
修改 cmder\config\cmder_prompt_config.lua
22行的 prompt_lambSymbol = "λ"
將 λ 改為 $
重啟 cmder
# 以 win + R 開啟 Cmder
右鍵 本機,內容 → 進階系統設定 → 進階 → 環境變數 → 編輯 系統變數底下的 Path
加上 Cmder 的目錄
之後只要使用 win+r,輸入 cmder 即可執行
要執行並切換到指定目錄的話,可輸入 cmder {路徑}
例如: cmder e:/wamp/www
# 新增 Cmder 到右鍵選單
用管理員權限開啟命令提示字元,輸入
Cmder.exe /REGISTER ALL
# 修改 Alias 加上常用指令
例如加上 ll=ls -l
修改 cmder\config\user_aliases.cmd
在 ls=ls --show-control-chars -F --color $* 下一行加上
ll=ls -l --show-control-chars -F --color $*
重啟 Cmder 即可
參考:
<a href="https://blog.miniasp.com/post/2015/09/27/Useful-tool-Cmder" target="_blank" rel="nofollow">介紹好用工具:Cmder (具有 Linux 溫度的 Windows 命令提示字元工具)</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-10-17 16:28:55 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-02 01:27:17 (台灣)</span></div></pre>
Knuckles
[PHP] 安裝套件管理器 Composer
http://disp.cc/b/11-gwdh
2023-09-24T23:56:18+08:00
2023-11-28T19:58:06+08:00
Composer 是PHP的軟體套件管理系統
用來管理套件彼此相依的問題
會在單個專案的目錄中安裝所有需要的程式庫
目前版本 2.6.3 需要 PHP 7.2+
PHP 5.3~7.1 要另外安裝 2.2.x 版
官網: https://getcomposer.org/
在 Linux 安裝 Composer
安裝環境: Rocky Linux 9
先安裝需要用到的程式
$ sudo yum install php-cli php-zip wget unzip
方法1 依照官網的說明使用 php 下載
下載 Composer 安裝程式
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
使用 php 執行 copy 將安裝程式下載為 composer-setup.php
檢查下載的 php 檔案完整性
$ HASH="$(wget -q -O - https://composer.github.io/installer ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [PHP] 安裝套件管理器 Composer <br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-09-24 Sun. 23:56:18</div><hr color="#008080" />
<div class="img" data-ori_w="959" data-ori_h="875" style="width:480px;height:438px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/d6bd137.png" alt="[圖]" /></div>
Composer 是PHP的軟體套件管理系統
用來管理套件彼此相依的問題
會在單個專案的目錄中安裝所有需要的程式庫
目前版本 2.6.3 需要 PHP 7.2+
PHP 5.3~7.1 要另外安裝 2.2.x 版
官網: <a href="https://getcomposer.org/" target="_blank" rel="nofollow">https://getcomposer.org/</a>
<span style="color:#00F000">在 Linux 安裝 Composer</span>
安裝環境: Rocky Linux 9
先安裝需要用到的程式
<code class="code-inline">$ sudo yum install php-cli php-zip wget unzip</code>
方法1 依照官網的說明使用 php 下載
下載 Composer 安裝程式
<code class="code-inline">$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"</code>
使用 php 執行 copy 將安裝程式下載為 composer-setup.php
檢查下載的 php 檔案完整性
<code class="code-inline">$ HASH="$(wget -q -O - https://composer.github.io/installer.sig)"</code>
<code class="code-inline">$ php -r "if (hash_file('SHA384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"</code>
顯示 Installer verified 代表沒問題
顯示 Installer corrupt 的話可能要重新下載
使用 php 執行安裝程式
<code class="code-inline">$ sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer</code>
--install-dir=/usr/local/bin 代表將執行檔放在執行路徑中
--filename=composer 代表將執行檔的檔名設定為 composer
方法2 使用 curl 下載
直接下載安裝程式並執行
<code class="code-inline">$ curl -sS <a href="https://getcomposer.org/installer" target="_blank" rel="nofollow">https://getcomposer.org/installer</a> -o composer-setup.php</code>
<code class="code-inline">$ php composer-setup.php --install-dir=/usr/local/bin --filename=composer</code>
安裝好後執行看看
<code class="code-inline">$ composer</code>
若找不到檔案,代表 /usr/local/bin 不在執行路徑中
再加個軟連結到執行路徑,例如 /usr/bin
<code class="code-inline">$ sudo ln -s /usr/local/bin/composer /usr/bin/composer</code>
執行結果:
<div class="highlight"><span></span> ______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.6.3 2023-09-15 09:38:21
Usage:
command [options] [arguments]
Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output
-n, --no-interaction Do not ask any interactive question
--profile Display timing and memory usage information
--no-plugins Whether to disable plugins.
--no-scripts Skips the execution of all scripts defined in composer.json file.
-d, --working-dir=WORKING-DIR If specified, use the given directory as working directory.
--no-cache Prevent use of the cache
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
...
</div>
安裝成功後可將安裝程式移除
<code class="code-inline">$ php -r "unlink('composer-setup.php');"</code>
<span style="color:#00F000">在 Windows 安裝 Composer</span>
在官網下載安裝檔執行即可
<a href="https://getcomposer.org/Composer-Setup.exe" target="_blank" rel="nofollow">https://getcomposer.org/Composer-Setup.exe</a>
詢問是否要用 Developer mode 安裝時,不需勾選
<span style="color:#00F000">在新專案使用 Composer</span>
開一個新的資料夾來測試
<code class="code-inline">$ mkdir test_composer</code>
<code class="code-inline">$ cd test_composer</code>
下載一個套件看看,在 <a href="https://packagist.org/" target="_blank" rel="nofollow">https://packagist.org/</a> 可以選擇想要安裝的 PHP 套件
例如要安裝 monolog/monolog 套件的話
在專案的資料夾裡執行
<code class="code-inline">$ composer require monolog/monolog</code>
執行結果:
<div class="highlight"><span></span>Info from <a href="https://repo.packagist.org:" target="_blank" rel="nofollow">https://repo.packagist.org:</a> #StandWithUkraine
Cannot use monolog/monolog's latest version 3.4.0 as it requires php >=8.1 which is not satisfied by your platform.
./composer.json has been created
Running composer update monolog/monolog
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking monolog/monolog (2.9.1)
- Locking psr/log (3.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Downloading psr/log (3.0.0)
- Downloading monolog/monolog (2.9.1)
- Installing psr/log (3.0.0): Extracting archive
- Installing monolog/monolog (2.9.1): Extracting archive
10 package suggestions were added by new dependencies, use <code class="code-inline">composer suggest</code> to see details.
Generating autoload files
1 package you are using is looking for funding.
Use the <code class="code-inline">composer fund</code> command to find out more!
No security vulnerability advisories found.
Using version ^2.9 for monolog/monolog
</div>
自動依目前 PHP 的版本安裝了適合的 2.9 版,而不是最新的 3.4 版
自動安裝了相依的套件 psr/log
查看資料夾,多了兩個檔案和一個 vendor 資料夾
composer.json 用來指定要使用哪些套件以及套件的版本
composer.lock 已下載安裝的套件資訊
vendor 下載的套件都放在這個資料夾裡
vendor/autoload.php 在PHP程式中require這個檔就可以使用所有套件
<code class="code-inline">$ vim composer.json</code>
<div class="highlight"><span></span><span class="p">{</span>
<span class="w"> </span><span class="nt">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">"monolog/monolog"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^2.9"</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</div>
可看到指定了一個套件,以及版本為 ^2.9
版本號 ^1.2.3 代表 >=1.2.3 且 <2.0.0 (第一位數固定)
~1.2.3 代表 >=1.2.3 且 <1.3.0 (前兩位數固定)
可以自己編輯 composer.json 檔,加上想要的套件及版本號後
執行 composer install 就會自動安裝套件了
寫一個使用套件的 PHP 程式
<code class="code-inline">$ vim test_monolog.php</code>
<div class="highlight"><span></span><span class="cp"><?php</span>
<span class="k">require</span> <span class="no">__DIR__</span> <span class="o">.</span> <span class="s1">'/vendor/autoload.php'</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Monolog\Logger</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Monolog\Handler\StreamHandler</span><span class="p">;</span>
<span class="c1">// create a log channel</span>
<span class="nv">$log</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Logger</span><span class="p">(</span><span class="s1">'name'</span><span class="p">);</span>
<span class="nv">$log</span><span class="o">-></span><span class="na">pushHandler</span><span class="p">(</span><span class="k">new</span> <span class="nx">StreamHandler</span><span class="p">(</span><span class="s1">'text.log'</span><span class="p">,</span> <span class="nx">Logger</span><span class="o">::</span><span class="na">WARNING</span><span class="p">));</span>
<span class="c1">// add records to the log</span>
<span class="nv">$log</span><span class="o">-></span><span class="na">warning</span><span class="p">(</span><span class="s1">'Foo'</span><span class="p">);</span>
<span class="nv">$log</span><span class="o">-></span><span class="na">error</span><span class="p">(</span><span class="s1">'Bar'</span><span class="p">);</span>
</div>
執行這個 PHP 程式
<code class="code-inline">$ php test_monolog.php</code>
資料夾下多了一個 test.log 檔
<div class="highlight"><span></span>[2023-09-24T23:47:29.798026+08:00] name.WARNING: Foo [] []
[2023-09-24T23:47:29.799463+08:00] name.ERROR: Bar [] []
</div>
<span style="color:#00F000">Composer 指令</span>
安裝指定的套件
<code class="code-inline">$ composer require 套件名稱</code>
安裝 composer.json 中有指定,但 composer.lock 中沒有的套件
<code class="code-inline">$ composer install</code>
更新所有的套件
<code class="code-inline">$ composer update</code>
更新指定的套件
<code class="code-inline">$ composer update 套件1 套件2</code>
移除指定的套件
<code class="code-inline">$ composer remove 套件名稱</code>
更新 composer 版本
<code class="code-inline">$ sudo composer self-update</code>
參考
<a href="https://phoenixnap.com/kb/how-to-install-and-use-php-composer-on-centos-7" target="_blank" rel="nofollow">https://phoenixnap.com/kb/how-to-install-and-use-php-composer-on-centos-7</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-09-24 23:56:18 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-28 19:58:06 (台灣)</span></div></pre>
Knuckles
[RockyLinux9] PHP 安裝與設定
http://disp.cc/b/11-gvSK
2023-09-22T20:03:39+08:00
2024-02-21T21:27:31+08:00
安裝環境: Linode 的 Rocky Linux 9
安裝 PHP
先安裝 Apache
[RockyLinux9] 網頁伺服器 Apache 安裝與設定 - KnucklesNote板 - Disp BBS
安裝 PHP
$ sudo dnf install php
==========================================================================
Package Arch Version Repository Size
==========================================================================
Installing:
php x86_64 8.0.27-1.el9_1 appstream 10 k
Installing dependencies:
libxslt x86_6 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [RockyLinux9] PHP 安裝與設定<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-09-22 Fri. 20:03:39</div><hr color="#008080" />
安裝環境: Linode 的 Rocky Linux 9
<span style="color:#00F000">安裝 PHP</span>
先安裝 Apache
<a href="https://disp.cc/b/KnucklesNote/gvK9" target="_blank" rel="nofollow">[RockyLinux9] 網頁伺服器 Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
安裝 PHP
<code class="code-inline">$ sudo dnf install php</code>
<div class="highlight"><span></span>==========================================================================
Package Arch Version Repository Size
==========================================================================
Installing:
php x86_64 8.0.27-1.el9_1 appstream 10 k
Installing dependencies:
libxslt x86_64 1.1.34-9.el9 appstream 240 k
nginx-filesystem noarch 1:1.20.1-14.el9 appstream 10 k
oniguruma x86_64 6.9.6-1.el9.5 appstream 217 k
php-common x86_64 8.0.27-1.el9_1 appstream 667 k
Installing weak dependencies:
php-cli x86_64 8.0.27-1.el9_1 appstream 3.1 M
php-fpm x86_64 8.0.27-1.el9_1 appstream 1.6 M
php-mbstring x86_64 8.0.27-1.el9_1 appstream 470 k
php-opcache x86_64 8.0.27-1.el9_1 appstream 512 k
php-pdo x86_64 8.0.27-1.el9_1 appstream 83 k
php-xml x86_64 8.0.27-1.el9_1 appstream 131 k
Transaction Summary
==========================================================================
</div>
Rocky Linux 9 預設安裝的是 PHP 8.0
且預設就會使用 php-fpm 來執行 php
啟動且設定開機執行 php-fpm
<code class="code-inline">$ sudo systemctl start php-fpm</code>
<code class="code-inline">$ sudo systemctl enable php-fpm</code>
安裝要用到的 php 的模組
<code class="code-inline">$ sudo dnf install php-gd php-mysqlnd</code>
安裝 Apache 的 FastCGI 模組
<code class="code-inline">$ sudo dnf install mod_fcgid</code>
重新啟動 httpd
<code class="code-inline">$ sudo systemctl restart httpd</code>
檢查看看有沒有裝成功
$ php -v
<div class="highlight"><span></span>PHP 8.0.27 (cli) (built: Jan 3 2023 16:17:26) ( NTS gcc x86_64 )
Copyright (c) The PHP Group
Zend Engine v4.0.27, Copyright (c) Zend Technologies
with Zend OPcache v8.0.27, Copyright (c), by Zend Technologies
</div>
利用 phpinfo() 檢查看看 httpd 與 php 是否正常
<code class="code-inline">$ sudo echo "<span style="color:#008080"><?php phpinfo(); ?></span>" > /var/www/html/test_php.php</code>
打開瀏覽器,網址輸入 http://(網址或IP位址)/test_php.php
可以看到 PHP 版本為 8.0.27 和各項 PHP 模組的設定值
<div class="img" data-ori_w="1331" data-ori_h="1215" style="width:666px;height:608px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/7142154.png" alt="[圖]" /></div>
<span style="color:#00F000">修改 PHP 設定檔</span>
<code class="code-inline">$ sudo vim /etc/php.ini</code>
<div class="highlight"><span></span><span class="c1">;不在網頁顯示error,而是記錄在 /var/log/httpd/error_log</span>
<span class="na">display_errors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Off</span>
<span class="na">log_errors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">On</span>
<span class="c1">;ignore_repeated_errors = Off</span>
<span class="na">ignore_repeated_errors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">On</span>
<span class="c1">;ignore_repeated_source = Off</span>
<span class="na">ignore_repeated_source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">On</span>
<span class="c1">;html_errors = On</span>
<span class="na">html_errors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Off</span>
<span class="na">設定error的log檔位置</span>
<span class="c1">;error_log = php_errors.log</span>
<span class="na">error_log</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/log/php_errors.log</span>
<span class="c1">;設定時區</span>
<span class="c1">;date.timezone =</span>
<span class="na">date.timezone</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Asia/Taipei</span>
<span class="c1">; session 的設定</span>
<span class="c1">; 儲存 session 的 cookie 存活時間(秒),設為0的話瀏覽器關掉即到期</span>
<span class="c1">;session.cookie_lifetime = 0</span>
<span class="na">session.cookie_lifetime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">3600</span>
<span class="c1">; session存活時間</span>
<span class="c1">;session.gc_maxlifetime = 1440</span>
<span class="na">session.gc_maxlifetime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">3600</span>
</div>
有修改設定的話要重啟 php-fpm, apache
<code class="code-inline">$ sudo systemctl restart php-fpm</code>
<code class="code-inline">$ sudo systemctl restart httpd</code>
<span style="color:#00F000">修改 php-fpm 設定檔</span>
<code class="code-inline">$ sudo vim /etc/php-fpm.d/www.conf</code>
以下為預設值,可視需要修改
<div class="highlight"><span></span><span class="k">[www]</span>
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">apache</span>
<span class="na">group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">apache</span>
<span class="na">listen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/run/php-fpm/www.sock</span>
<span class="na">listen.acl_users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">apache,nginx</span>
<span class="na">listen.allowed_clients</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">127.0.0.1</span>
<span class="c1">;可依記憶體大小調整這些值,會影響效能</span>
<span class="na">pm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dynamic</span>
<span class="na">pm.max_children</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">50</span>
<span class="na">pm.start_servers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5</span>
<span class="na">pm.min_spare_servers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5</span>
<span class="na">pm.max_spare_servers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">35</span>
<span class="c1">;如果有設定 request_slowlog_timeout 的話,將執行過久的記錄在這</span>
<span class="na">slowlog</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/log/php-fpm/www-slow.log</span>
<span class="c1">;執行 php-fpm 時的錯誤記錄檔</span>
<span class="na">php_admin_value[error_log]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/log/php-fpm/www-error.log</span>
<span class="na">php_admin_flag[log_errors]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">on</span>
<span class="c1">;session 的儲存方式</span>
<span class="na">php_value[session.save_handler]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">files</span>
<span class="na">php_value[session.save_path]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/lib/php/session</span>
<span class="na">php_value[soap.wsdl_cache_dir]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/lib/php/wsdlcache</span>
<span class="c1">;用來儲存 opcache 檔案的路徑</span>
<span class="na">php_value[opcache.file_cache]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/var/lib/php/opcache</span>
</div>
有修改設定的話要重啟 php-fpm, apache
<code class="code-inline">$ sudo systemctl restart php-fpm</code>
<code class="code-inline">$ sudo systemctl restart httpd</code>
<span style="color:#00F000">設定 Opcache </span>
opcache (Optimizer Plus Cache) 可將編譯好的PHP存在記憶體中,
再次使用時就可以減少執行時間,預設已安裝了
編輯設定檔
<code class="code-inline">$ sudo vim /etc/php.d/10-opcache.ini</code>
<div class="highlight"><span></span><span class="c1">;將以下設定的註解取消</span>
<span class="c1">;要用來儲存opcache的記憶體大小</span>
<span class="na">opcache.memory_consumption</span><span class="o">=</span><span class="s">128</span>
<span class="na">opcache.interned_strings_buffer</span><span class="o">=</span><span class="s">8</span>
<span class="c1">;要用來加速的檔案數量</span>
<span class="na">opcache.max_accelerated_files</span><span class="o">=</span><span class="s">10000</span>
<span class="c1">;用來儲存 opcache 檔案的路徑,寫在 php-fpm 的設定檔</span>
<span class="c1">;opcache.file_cache=</span>
</div>
參考:
<a href="https://www.linode.com/docs/guides/how-to-install-and-configure-fastcgi-and-php-fpm-on-centos-8/" target="_blank" rel="nofollow">https://www.linode.com/docs/guides/how-to-install-and-configure-fastcgi-and-php-fpm-on-centos-8/</a>
<a href="https://docs.rockylinux.org/guides/web/php/" target="_blank" rel="nofollow">https://docs.rockylinux.org/guides/web/php/</a>
<span style="color:#00F000">更新 PHP 為 8.1 版</span>
參考 <a href="https://www.linuxcapable.com/how-to-install-php-on-rocky-linux/" target="_blank" rel="nofollow">https://www.linuxcapable.com/how-to-install-php-on-rocky-linux/</a>
RockyLinux9 只提供 PHP 到 8.0 版,換成新版的話,要安裝 EPEL 和 Remi 軟體庫
<code class="code-inline">$ sudo yum install epel-release</code>
安裝 Remi repository for RockyLinux 9
<code class="code-inline">$ sudo dnf install dnf-utils <a href="http://rpms.remirepo.net/enterprise/remi-release-9.rpm" target="_blank" rel="nofollow">http://rpms.remirepo.net/enterprise/remi-release-9.rpm</a></code>
啟用 CRB (Code Ready Builder)
<code class="code-inline">$ sudo dnf config-manager --set-enabled crb</code>
安裝 dnf-utils (原本的 yum-utils)
<code class="code-inline">$ sudo dnf install dnf-utils</code>
查看 Remi 軟體庫中能使用的 PHP 版本
<code class="code-inline">$ sudo dnf module list php</code>
<div class="highlight"><span></span>Remi's Modular repository for Enterprise Linux 9 - x86_64 246 kB/s | 647 kB 00:02
Safe Remi's RPM repository for Enterprise Linux 9 - x86_64 392 kB/s | 975 kB 00:02
Last metadata expiration check: 0:00:01 ago on Wed 21 Feb 2024 06:59:54 PM CST.
Rocky Linux 9 - AppStream
Name Stream Profiles Summary
php 8.1 common [d], devel, minimal PHP scripting language
Remi's Modular repository for Enterprise Linux 9 - x86_64
Name Stream Profiles Summary
php remi-7.4 common [d], devel, minimal PHP scripting language
php remi-8.0 common [d], devel, minimal PHP scripting language
php remi-8.1 common [d], devel, minimal PHP scripting language
php remi-8.2 common [d], devel, minimal PHP scripting language
php remi-8.3 common [d], devel, minimal PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled
</div>
啟用 PHP 8.1
<code class="code-inline">$ sudo dnf module enable php:remi-8.1</code>
更新 PHP 與相關模組
<code class="code-inline">$ sudo dnf update php</code>
<div class="highlight"><span></span>Upgraded:
php-8.1.27-1.el9.remi.x86_64 php-cli-8.1.27-1.el9.remi.x86_64
php-common-8.1.27-1.el9.remi.x86_64 php-fpm-8.1.27-1.el9.remi.x86_64
php-gd-8.1.27-1.el9.remi.x86_64 php-mbstring-8.1.27-1.el9.remi.x86_64
php-mysqlnd-8.1.27-1.el9.remi.x86_64 php-opcache-8.1.27-1.el9.remi.x86_64
php-pdo-8.1.27-1.el9.remi.x86_64 php-pecl-igbinary-3.2.15-1.el9.remi.8.1.x86_64
php-pecl-msgpack-2.2.0-1.el9.remi.8.1.x86_64 php-pecl-redis5-5.3.7-1.el9.remi.8.1.x86_64
php-pecl-zip-1.22.3-1.el9.remi.8.1.x86_64 php-xml-8.1.27-1.el9.remi.x86_64
Installed:
fribidi-1.0.10-6.el9.2.x86_64 gd3php-2.3.3-9.el9.remi.x86_64 gdk-pixbuf2-2.42.6-3.el9.x86_64
highway-1.0.7-1.el9.x86_64 jxl-pixbuf-loader-0.7.0-1.el9.x86_64 libaom-3.8.0-1.el9.x86_64
libavif-0.11.1-4.el9.x86_64 libdav1d-1.2.1-1.el9.x86_64 libimagequant-2.17.0-1.el9.x86_64
libjxl-0.7.0-1.el9.x86_64 libraqm-0.8.0-1.el9.x86_64 libsodium-1.0.18-8.el9.x86_64
libvmaf-2.3.0-2.el9.x86_64 oniguruma5php-6.9.9-1.el9.remi.x86_64 php-sodium-8.1.27-1.el9.remi.x86_64
rav1e-libs-0.7.1-1.el9.x86_64 remi-libzip-1.10.1-1.el9.remi.x86_64 shared-mime-info-2.1-5.el9.x86_64
</div>
重啟 apache 與 php-fpm
<code class="code-inline">$ sudo systemctl restart httpd</code>
<code class="code-inline">$ sudo systemctl restart php-fpm</code>
查看目前 PHP 版本
<code class="code-inline">$ php -v</code>
<div class="highlight"><span></span>PHP 8.1.27 (cli) (built: Dec 19 2023 20:35:55) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.1.27, Copyright (c) Zend Technologies
with Zend OPcache v8.1.27, Copyright (c), by Zend Technologies
</div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-09-22 20:03:39 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2024-02-21 21:27:30 (台灣)</span></div></pre>
Knuckles
[RockyLinux9] 網頁伺服器 Apache 安裝與設定
http://disp.cc/b/11-gvK9
2023-09-22T01:11:43+08:00
2023-11-26T23:51:15+08:00
安裝環境: Linode 的 Rocky Linux 9
參考: [Linode] 新增 RockyLinux9 主機 - KnucklesNote板
安裝 Apache
$ sudo dnf install httpd
Rocky Linux 9 安裝的是 apache 2.4.53
啟動 httpd
$ sudo systemctl start httpd
設定 Apache 服務隨系統一起啟動
$ sudo systemctl enable httpd
查看執行狀態
$ sudo systemctl status httpd
防火牆開啟 80 與 443 port
$ sudo firewall-cmd --permanent --zone=public --add-service={http,https}
$ sudo firewall-cmd --reload
用瀏覽器連看看能不能看到 Apache 的預設畫面
修改 Apache 設定檔
查看 Apache 的工作模式
$ sudo httpd -V
Server version: Apache/2.4.53 (Rocky ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [RockyLinux9] 網頁伺服器 Apache 安裝與設定<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-09-22 Fri. 01:11:42</div><hr color="#008080" />
安裝環境: Linode 的 Rocky Linux 9
參考: <a href="https://disp.cc/b/KnucklesNote/gvju" target="_blank" rel="nofollow">[Linode] 新增 RockyLinux9 主機 - KnucklesNote板</a>
<span style="color:#00F000">安裝 Apache</span>
<code class="code-inline">$ sudo dnf install httpd</code>
Rocky Linux 9 安裝的是 apache 2.4.53
啟動 httpd
<code class="code-inline">$ sudo systemctl start httpd</code>
設定 Apache 服務隨系統一起啟動
<code class="code-inline">$ sudo systemctl enable httpd</code>
查看執行狀態
<code class="code-inline">$ sudo systemctl status httpd</code>
防火牆開啟 80 與 443 port
<code class="code-inline">$ sudo firewall-cmd --permanent --zone=public --add-service={http,https}</code>
<code class="code-inline">$ sudo firewall-cmd --reload</code>
用瀏覽器連看看能不能看到 Apache 的預設畫面
<div class="img" data-ori_w="1076" data-ori_h="1015" style="width:538px;height:508px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/02f0b6a.png" alt="[圖]" /></div>
<span style="color:#00F000">修改 Apache 設定檔</span>
查看 Apache 的工作模式
<code class="code-inline">$ sudo httpd -V</code>
<div class="highlight"><span></span>Server version: Apache/2.4.53 (Rocky Linux)
Server built: Apr 28 2023 00:00:00
Server's Module Magic Number: 20120211:124
Server loaded: APR 1.7.0, APR-UTIL 1.6.1, PCRE 8.44 2020-02-12
Compiled using: APR 1.7.0, APR-UTIL 1.6.1, PCRE 8.44 2020-02-12
Architecture: 64-bit
Server MPM: event
threaded: yes (fixed thread count)
forked: yes (variable process count)
Server compiled with....
</div>
可以看到 Server MPM: event
代表多工處理模式(MPM, Multi-Processing Module)預設是使用 event
Apache 的主要設定檔在 /etc/httpd/conf/httpd.conf
基本上不要改這個檔,而是將要修改的設定另外寫在 /etc/httpd/conf.d/ 裡
例如新增一個 /etc/httpd/conf.d/common.conf
<code class="code-inline">$ sudo vim /etc/httpd/conf.d/common.conf</code>
<div class="highlight"><span></span><span class="c1"># 設定 ServerName,將 xxx.xxx.xxx.xxx 改成主機的網址或IP位址</span>
ServerName<span class="w"> </span>xxx.xxx.xxx.xxx:80
<span class="c1"># 網址沒有指定檔名時,預設開啟的檔名</span>
<span class="c1"># 預設 DirectoryIndex index.html </span>
<span class="c1"># 加上常用的 index.htm </span>
<span class="c1"># 不用加 index.php 安裝php後會自動加在 /etc/httpd/conf.d/php.conf</span>
DirectoryIndex<span class="w"> </span>index.html<span class="w"> </span>index.htm
<span class="c1"># 記憶體管理模式使用 event 時,依記憶體大小調整以下設定</span>
<IfModule<span class="w"> </span>event.c>
<span class="w"> </span>StartServers<span class="w"> </span><span class="m">2</span>
<span class="w"> </span>MinSpareThreads<span class="w"> </span><span class="m">25</span>
<span class="w"> </span>MaxSpareThreads<span class="w"> </span><span class="m">75</span>
<span class="w"> </span>ThreadLimit<span class="w"> </span><span class="m">64</span>
<span class="w"> </span>ThreadsPerChild<span class="w"> </span><span class="m">25</span>
<span class="w"> </span>MaxRequestWorkers<span class="w"> </span><span class="m">150</span>
<span class="w"> </span>MaxConnectionsPerChild<span class="w"> </span><span class="m">0</span>
</IfModule>
</div>
若沒有設定 ServerName 的話會出現警告
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.x.x. Set the 'ServerName' directive globally to suppress this message
先測試看看有沒有問題
<code class="code-inline">$ sudo apachectl configtest</code>
或是
<code class="code-inline">$ sudo httpd -t</code>
Syntax OK
重新載入設定檔
<code class="code-inline">$ sudo systemctl reload httpd</code>
也可以在不中斷使用者連線的情況下更新設定檔
<code class="code-inline">$ sudo apachectl graceful</code>
<span style="color:#00F000">設定虛擬主機 Virtual Host</span>
例如我有申請一個網址 mydomain.com 會轉為主機的IP位址
我想要讓這個網址連進來是連到 /var/www/mydomain/ 這個資料夾
連線的記錄檔放在 /var/log/httpd/ 資料夾
先在 /var/www/ 新增 mydomain 資料夾
<code class="code-inline">$ sudo mkdir /var/www/mydomain</code>
設定權限讓 apache 可以讀寫
<code class="code-inline">$ sudo chown apache.apache /var/www/mydomain -R</code>
新增 vhost.conf
<code class="code-inline">$ sudo /etc/httpd/conf.d/vhost.conf</code>
<div class="highlight"><span></span><span class="c1"># 預設的網頁資料夾</span>
<VirtualHost<span class="w"> </span>*:80>
<span class="w"> </span>DocumentRoot<span class="w"> </span>/var/www/html
</VirtualHost>
<span class="w"> </span>
<VirtualHost<span class="w"> </span>*:80>
<span class="w"> </span>ServerName<span class="w"> </span>mydomain.com
<span class="w"> </span><span class="c1"># 如果還有其他網址要寫在 ServerAlias</span>
<span class="w"> </span>ServerAlias<span class="w"> </span>www.mydomain.com<span class="w"> </span>mydomain2.com
<span class="w"> </span>DocumentRoot<span class="w"> </span>/var/www/mydomain
<span class="w"> </span><Directory<span class="w"> </span><span class="s2">"/var/www/mydomain"</span>>
<span class="w"> </span>Options<span class="w"> </span>FollowSymLinks
<span class="w"> </span>AllowOverride<span class="w"> </span>All
<span class="w"> </span><span class="c1"># 下面這行是用來取代之前的 Allow from all</span>
<span class="w"> </span>Require<span class="w"> </span>all<span class="w"> </span>granted<span class="w"> </span>
<span class="w"> </span></Directory>
<span class="w"> </span>ErrorLog<span class="w"> </span><span class="s2">"/var/log/httpd/mydomain.error.log"</span>
<span class="w"> </span>CustomLog<span class="w"> </span><span class="s2">"/var/log/httpd/mydomain.access.log"</span><span class="w"> </span>combined
</VirtualHost>
</div>
測試並重啟httpd
<code class="code-inline">$ sudo apachectl configtest</code>
<code class="code-inline">$ sudo restart httpd</code>
<span style="color:#00F000">SELinux 設定</span>
因為預設有開啟 SELinux 的關係,網頁資料夾若放在 /var/www 以外的地方,
重啟 httpd 會出現錯誤:
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
查看目前的 SELinux 模式
<code class="code-inline">$ sudo getenforce</code>
Enforcing
將 SELinux 改為寬容模式(permissive)
<code class="code-inline">$ sudo setenforce 0</code>
重啟 httpd 看看,若沒問題那就是 SELinux 擋住的關係
將 SELinux 改為強制模式(Enforcing)
<code class="code-inline">$ sudo setenforce 1</code>
允許 httpd 可以連網路
<code class="code-inline">$ setsebool -P httpd_can_network_connect on</code>
允許可以存取 NFS
<code class="code-inline">$ setsebool -P httpd_use_nfs on</code>
需要上傳檔案的資料夾
<code class="code-inline">$ chcon -R -t httpd_sys_rw_content_t {資料夾路徑}</code>
相關文章:
<a href="https://disp.cc/b/KnucklesNote/9SKl" target="_blank" rel="nofollow">[CentOS7] Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/czOq" target="_blank" rel="nofollow">[CentOS7] Apache 使用 Certbot 申請 Let's Encrypt 的SSL憑證 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/8nyA" target="_blank" rel="nofollow">[Apache] log分析工具 AWStats - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/gF6f" target="_blank" rel="nofollow">[Apache] 讀取log檔的流量分析工具 GoAccess - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/86jr" target="_blank" rel="nofollow">[Apache] 安裝 PageSpeed 模組 改善網頁速度 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/68g3" target="_blank" rel="nofollow">[Apache] mod_evasive 阻擋DDoS攻擊 - KnucklesNote板 - Disp BBS</a>
參考:
<a href="https://www.linode.com/docs/guides/how-to-install-a-lamp-stack-on-centos-8/" target="_blank" rel="nofollow">https://www.linode.com/docs/guides/how-to-install-a-lamp-stack-on-centos-8/</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-09-22 01:11:43 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-26 23:51:15 (台灣)</span></div></pre>
Knuckles
[Linux] 防火牆 firewalld 安裝與設定
http://disp.cc/b/11-gvDH
2023-09-21T17:33:23+08:00
2023-09-21T22:50:36+08:00
舊版 CentOS Linux 的防火牆是用傳統的 iptables
但每次修改規則都清除原本的設定,再重新設定所有的規則
必需寫一個 shell script 檔來執行才行
所以從 CentOS 7 開始預設改用比較方便的 firewalld
安裝 firewalld
如果有使用 iptables 的話要先移除
檢查是否有使用 iptables
$ systemctl status iptables
有的話停止正在執行的 iptables
$ sudo systemctl stop iptables
將 iptables 永久關閉
$ sudo systemctl mask iptables
安裝 firwalld
$ sudo yum install firewalld
檢查 firewalld 執行狀態
$ systemctl status firewalld
啟動並設定開機自動執行
$ sudo systemctl start firewalld
$ sudo systemctl enable firewalld
firewalld 的區域設定
firewalld 的設定都是使 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Linux] 防火牆 firewalld 安裝與設定<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-09-21 Thu. 17:33:23</div><hr color="#008080" />
舊版 CentOS Linux 的防火牆是用傳統的 iptables
但每次修改規則都清除原本的設定,再重新設定所有的規則
必需寫一個 shell script 檔來執行才行
所以從 CentOS 7 開始預設改用比較方便的 firewalld
<span style="color:#00F000">安裝 firewalld</span>
如果有使用 iptables 的話要先移除
檢查是否有使用 iptables
$ systemctl status iptables
有的話停止正在執行的 iptables
$ sudo systemctl stop iptables
將 iptables 永久關閉
$ sudo systemctl mask iptables
安裝 firwalld
$ sudo yum install firewalld
檢查 firewalld 執行狀態
$ systemctl status firewalld
啟動並設定開機自動執行
$ sudo systemctl start firewalld
$ sudo systemctl enable firewalld
<span style="color:#00F000">firewalld 的區域設定</span>
firewalld 的設定都是使用 firewall-cmd 指令
firewalld 對於不同的區域可以使用不同的設定
列出已經定義好的區域
$ sudo firewall-cmd --get-zones
block dmz drop external home internal nm-shared public trusted work
block: 阻檔所有與外部的連線
drop: 只允許連出去,阻檔所有進來的連線
home, work, internal: 家用、工作用、內部網路,比較不會有攻擊者
public: 公開網路,可能會有攻擊者,大部份的連線設定寫在這裡
trusted: 信任的網路,允許所有連線
列出所有區域與其設定
$ sudo firewall-cmd --list-all-zones
查詢某個區域的設定
$ sudo firewall-cmd --zone=public --list-all
<div class="highlight"><span></span>public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
</div>
上面顯示的設定是目前使用的,若要查詢永久設定,使用
$ sudo firewall-cmd --zone=public --list-all --permanent
目前預設的區域,沒指定區域的話就會用預設的區域
$ sudo firewall-cmd --get-default-zone
public
更改預設的區域
$ sudo firewall-cmd --set-default-zone=work
查詢正在運作的區域
$ sudo firewall-cmd --get-active-zones
public
interfaces: eth0
每個連線會依以下順序來選擇要套用哪個區域的規則
1. 首先看 IP 是否符合某個區域設定的 sources
2. 沒有的話看 interfaces
3. 都不符合的話就用預設的區域
例如要將某個IP的連線套用 trusted 區域的設定
$ sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.0.1
$ sudo firewall-cmd --reload
這樣與 192.168.0.1 的連線就會使用 trusted 區域的設定一律允許了
<span style="color:#00F000">修改連線規則</span>
要增加新的服務,例如 http 的話
$ sudo firewall-cmd --zone=public --add-service=http --permanent
--permanent 加這個代表寫入硬碟的永久設定,要 --reload 才會生效,沒寫的話就是暫時增加
要移除某個服務
$ sudo firewall-cmd --zone=public --remove-service=http --permanent
新增一個 port,例如 3306
$ sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent
要移除某個 port 的話
$ sudo firewall-cmd --zone=public --remove-port=3306/tcp --permanent
設定好要重載才會生效
$ sudo firewall-cmd --reload
<span style="color:#00F000">允許或禁止特定的IP連線</span>
可以將連線的IP加入 trusted 或 block 區域,
或是在 public 區域另外加上 rich-rule 設定
例如允許內部IP 192.168.0.1 的所有連線
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.0.1" accept'
允許內部IP 192.168.0.x 的所有連線
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.0.0/24" accept'
例如禁止IP 192.168.0.1 的所有連線
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.0.1" reject'
設定好要重載才會生啟
$ sudo firewall-cmd --reload
查看設定好的 rich-rule
$ sudo firewall-cmd --list-all
<div class="highlight"><span></span>public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
<span class="tab"> </span>rule family="ipv4" source address="192.168.0.1" accept
</div>
要移除 rich-rule,就把上面的指令 --add-rich-rule 改成 --remove-rich-rule
參考
<a href="https://blog.gtwang.org/linux/centos-7-firewalld-command-setup-tutorial/2/" target="_blank" rel="nofollow">CentOS Linux 7 以 firewalld 指令設定防火牆規則教學</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-09-21 17:33:23 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-09-21 22:50:36 (台灣)</span></div></pre>
Knuckles
[Linode] 新增 RockyLinux9 主機
http://disp.cc/b/11-gvju
2023-09-19T20:01:23+08:00
2023-11-18T18:04:10+08:00
CentOS 7 之後,Red Hat公司停止開發 CentOS 了
而 CentOS 的創始人另外推出 RockyLinux 來延續 CentOS
所以也跟著改用 RockyLinux
還沒有註冊 Linode 帳號的話,可以用我的推薦連結: 登入Linode
點「Create」/「Linode」
Image 選「RockyLinux9」
Region 選日本的機房「Tokyo, JP (ap-northeast)」
Plan 只是測試的話,選共享CPU的最小方案「Nano 1GB」,每月只要美金$5
Linode Label 幫這個 Linode 取一個名字,例如「www4」
Root Password 輸入管理員 root 的密碼
SSH Keys 輸入的公鑰會存在 /root/.ssh/authorized_keys
但之後不使用 root 登入所以略過
Private IP 打勾,點「Create Linode」
等 Linode 安裝好,狀態會顯示 Running
在 IP Address 後面按 Copy 複製登入的 IP
使用 ssh 登入 root@139.162. ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Linode] 新增 RockyLinux9 主機<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-09-19 Tue. 20:01:23</div><hr color="#008080" />
CentOS 7 之後,Red Hat公司停止開發 CentOS 了
而 CentOS 的創始人另外推出 RockyLinux 來延續 CentOS
所以也跟著改用 RockyLinux
還沒有註冊 Linode 帳號的話,可以用我的推薦連結: <a href="https://www.linode.com/lp/refer/?r=1f089b0f1d2eb2b16bd26a0b4ce7c67478d78f4d" target="_blank" rel="nofollow">登入Linode</a>
點「Create」/「Linode」
<div class="img" data-ori_w="1361" data-ori_h="1151" style="width:681px;height:576px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/bacacad1.png" alt="[圖]" /></div>
Image 選「RockyLinux9」
Region 選日本的機房「Tokyo, JP (ap-northeast)」
Plan 只是測試的話,選共享CPU的最小方案「Nano 1GB」,每月只要美金$5
<div class="img" data-ori_w="813" data-ori_h="818" style="width:407px;height:409px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/fc1d675.png" alt="[圖]" /></div>
Linode Label 幫這個 Linode 取一個名字,例如「www4」
Root Password 輸入管理員 root 的密碼
SSH Keys 輸入的公鑰會存在 /root/.ssh/authorized_keys
但之後不使用 root 登入所以略過
<div class="img" data-ori_w="1024" data-ori_h="429" style="width:512px;height:215px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/73264ab.png" alt="[圖]" /></div>
Private IP 打勾,點「Create Linode」
等 Linode 安裝好,狀態會顯示 Running
<div class="img" data-ori_w="876" data-ori_h="350" style="width:438px;height:175px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/aec0aa8.png" alt="[圖]" /></div>
在 IP Address 後面按 Copy 複製登入的 IP
使用 ssh 登入 root@139.162.x.x
輸入設定的 root 密碼,就可以登入了
例如使用 Xshell 的話
<div class="img" data-ori_w="1115" data-ori_h="495" style="width:558px;height:248px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/cad3abd.png" alt="[圖]" /></div>
登入後會顯示
<div class="highlight"><span></span>Last login: Tue Sep 19 14:37:43 2023 from 125.228.137.114
[root@localhost ~]#
</div>
若出現 WARNING! The remote SSH server rejected X11 forwarding request.
將 Xshell 的 X11 轉寄關掉即可
右鍵點連線的內容->SSH->通道,取消「轉寄X11連線到:」
<span style="color:#00F000">修改主機名稱</span>
預設主機名稱為 localhost,例如要改為 www4
<code class="code-inline"># hostnamectl set-hostname www4</code>
重新登入後,提示字元會變成
<code class="code-inline">[root@www4 ~]#</code>
取得內部 IP,到 Linode 的 Network 頁
<div class="img" data-ori_w="813" data-ori_h="521" style="width:407px;height:261px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/5a227c8.png" alt="[圖]" /></div>
找到 192.168 開頭的 IP,點複製
<div class="img" data-ori_w="693" data-ori_h="281" style="width:347px;height:141px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/1b47ae8.png" alt="[圖]" /></div>
在這台主機與其他區網內的主機,設定 hosts 表
<code class="code-inline"># vim /etc/hosts</code>
加上內部 IP 與主機名稱
192.168.x.x www4
之後其他主機就可以用 ssh www4 來登入這台主機了
[NOTE] vim的操作方法,按 i 切換成輸入模式,輸入完按 Esc 回控制模式,按 :x 存檔離開
<span style="color:#00F000">更新軟體到最新版</span>
原本的套件管理程式 yum,要改為使用 dnf 了
但 yum 還是可以用,dnf 的指令也都跟 yum 一樣
為了避免安全漏洞先更新軟體
<code class="code-inline"># dnf update</code>
<span style="color:#00F000">更新系統時區</span>
預設是使用 UTC 時間,改成時區 Asia/Taipei
<code class="code-inline"># timedatectl set-timezone 'Asia/Taipei'</code>
查看現在的時間是否顯示正確
<code class="code-inline"># timedatectl</code>
<div class="highlight"><span></span> Local time: Tue 2023-09-19 23:12:31 CST
Universal time: Tue 2023-09-19 15:12:31 UTC
RTC time: Tue 2023-09-19 15:12:31
Time zone: Asia/Taipei (CST, +0800)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
</div>
預設已開啟 NTP 同步時間了
<span style="color:#00F000">設定防火牆</span>
預設已使用 Firewalld 了
查看 firewalld 執行狀態
<code class="code-inline"># systemctl status firewalld</code>
顯示 Active: active (running) 就是正常執行
查看目前防火牆設定
<code class="code-inline"># firewall-cmd --list-all</code>
<div class="highlight"><span></span>public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
</div>
預設已開啟 ssh 了
要增加新的服務,例如 http 的話
<code class="code-inline"># firewall-cmd --zone=public --add-service=http --permanent</code>
--permanent 代表永久增加,沒寫的話就是暫時增加
要移除某個服務,例如 http 的話
<code class="code-inline"># firewall-cmd --zone=public --remove-service=http --permanent</code>
新增一個 port,例如 3306
<code class="code-inline"># firewall-cmd --zone=public --add-port=3306/tcp --permanent</code>
要移除某個 port 的話
<code class="code-inline"># firewall-cmd --zone=public --remove-port=3306/tcp --permanent</code>
設定好要重載才會生效
<code class="code-inline"># firewall-cmd --reload</code>
<span style="color:#00F000">新增一個使用金鑰登入的管理員帳號</span>
參考 <a href="http://disp.cc/b/11-8JAD" target="_blank" rel="nofollow">[Xshell] Windows 下好用的 SSH 登入程式 - KnucklesNote板</a>
<a href="http://disp.cc/b/11-6whK" target="_blank" rel="nofollow">[CentOS] 主機間使用公鑰免密碼登入ssh - KnucklesNote板</a>
例如我要新增的帳號為 knuckles,執行
<code class="code-inline"># useradd knuckles</code>
接著設定新帳號的密碼,執行
<code class="code-inline"># passwd knuckles</code>
將新的帳號加入管理員權限
使用 <code class="code-inline"># visudo</code> 來修改 /etc/sudoer 檔
在 <code class="code-inline">root ALL=(ALL) ALL</code>
這行下面加上其他管理員的帳號,例如
<code class="code-inline">knuckles ALL=(ALL) ALL</code>
使用 <code class="code-inline"># su knuckles</code> 切換為新帳號
提示字元會變成
<code class="code-inline">[knuckles@localhost root]$</code>
非 root 帳號若要執行需要 root 權限的指令時
只要加上 sudo 即可執行
在新帳號的家目錄新增權限為 700 的 .ssh 目錄
<code class="code-inline">$ mkdir ~/.ssh; chmod 700 ~/.ssh</code>
在 .ssh 目錄下新增權限為 600 的 authorized_keys 檔案
將 ssh 登入程式使用的公鑰寫進去
<code class="code-inline">$ vim ~/.ssh/authorized_keys</code>
<code class="code-inline">$ chmod 600 ~/.ssh/authorized_keys</code>
測試一下 SSH 登入軟體是否能用金鑰登入新的帳號
是否可以執行 <code class="code-inline">$ sudo su</code> 後輸入新帳號的密碼,切換為 root 帳號
沒問題的話,將 ssh 的密碼登入關閉,以增加安全性
<code class="code-inline">$ sudo vim /etc/ssh/sshd_config</code>
尋找 <span style="color:#008000">#PasswordAuthentication yes</span>
改成 PasswordAuthentication no
要使用多個 Port 例如 <span style="color:#008080">12345</span> 也可以登入的話,將
<span style="color:#008000"># Port 22</span> 改為
Port 22
Port <span style="color:#008080">12345</span>
重啟 ssh
<code class="code-inline">$ sudo systemctl restart sshd</code>
防火牆開啟 port,例如 <span style="color:#008080">12345</span>
<code class="code-inline">$ sudo firewall-cmd --zone=public --add-port=<span style="color:#008080">12345</span>/tcp --permanent</code>
<code class="code-inline">$ sudo firewall-cmd --reload</code>
使用 SSH 登入軟體測試一下使用 port <span style="color:#008080">12345</span> 能不能登入
不能的話可能是有開啟 SELinux 限制只能用 port 22
安裝 SELinux 設定工具 semanage
<code class="code-inline">$ sudo dnf install policycoreutils-python-utils</code>
設定 SELinux 加上 ssh 可以使用 port 12345
<code class="code-inline">$ sudo semanage port -a -t ssh_port_t -p tcp <span style="color:#008080">12345</span></code>
重啟 ssh
<code class="code-inline">$ sudo systemctl restart sshd</code>
確定 port <span style="color:#008080">12345</span> 可以登入後,再將 port 22 用防火牆擋住
<code class="code-inline">$ sudo firewall-cmd --zone=public --remove-service=ssh --permanent</code>
<code class="code-inline">$ sudo firewall-cmd --reload</code>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-09-19 20:01:23 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-18 18:04:09 (台灣)</span></div></pre>
Knuckles
[Linode] NodeBalancer 使用 Let's Encrypt SSL憑證
http://disp.cc/b/11-gfCN
2023-07-04T02:58:47+08:00
2023-11-29T23:20:45+08:00
Linode 的負載分流器 NodeBalancer
可以使用 HTTPS 加密模式,但需要輸入通過認證的 SSL 憑證
要申請免費的 Let's Encrypt SSL憑證的話
先依照這篇的方法: [CentOS7] Apache 使用 Certbot 申請 Let's Encrypt 的SSL憑證
使用 snap 安裝 certbot
但當 NodeBalancer 連結兩台以上主機時,若使用 HTTP-01 Challenge 認證
認證時可能會連到沒有確認檔的主機導致認證失敗
必須改用 DNS-01 Challenge 認證,將確認檔寫在DNS上
申請 Linode API 的 Personal Access Token
用來自動更新 Linode 的 Domains 與 NodeBalancer
在 Linode 管理頁面右上角點 My Profile 的 API Tokens
點「Create A Personal Access Token」
Label輸入「update_ssl」,過期時間選擇「Never」
點一下Select All的「None」後 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Linode] NodeBalancer 使用 Let's Encrypt SSL憑證<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2023-07-04 Tue. 02:58:47</div><hr color="#008080" />
<div class="img" data-ori_w="1024" data-ori_h="291" style="width:512px;height:146px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/443740a.png" alt="[圖]" /></div>
Linode 的負載分流器 NodeBalancer
可以使用 HTTPS 加密模式,但需要輸入通過認證的 SSL 憑證
要申請免費的 Let's Encrypt SSL憑證的話
先依照這篇的方法: <a href="https://disp.cc/b/KnucklesNote/czOq" target="_blank" rel="nofollow">[CentOS7] Apache 使用 Certbot 申請 Let's Encrypt 的SSL憑證</a>
使用 snap 安裝 certbot
但當 NodeBalancer 連結兩台以上主機時,若使用 <a href="https://letsencrypt.org/zh-tw/docs/challenge-types/#http-01-%E8%80%83%E9%A9%97" target="_blank" rel="nofollow">HTTP-01 Challenge</a> 認證
認證時可能會連到沒有確認檔的主機導致認證失敗
必須改用 <a href="https://letsencrypt.org/zh-tw/docs/challenge-types/#dns-01-%E8%80%83%E9%A9%97" target="_blank" rel="nofollow">DNS-01 Challenge</a> 認證,將確認檔寫在DNS上
<span style="color:#00F000">申請 Linode API 的 Personal Access Token</span>
用來自動更新 Linode 的 Domains 與 NodeBalancer
在 Linode 管理頁面右上角點 My Profile 的 API Tokens
點「Create A Personal Access Token」
<div class="img" data-ori_w="1024" data-ori_h="254" style="width:512px;height:127px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/34d64f5.png" alt="[圖]" /></div>
Label輸入「update_ssl」,過期時間選擇「Never」
點一下Select All的「None」後,再點 Domains 與 NodeBalancers 的「Read/Write」
<div class="img" data-ori_w="599" data-ori_h="1490" style="width:300px;height:745px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/43727c4.png" alt="[圖]" /></div>
點下面的「Create Token」後會產生一串 Personal Access Token,先複製起來存著
<div class="img" data-ori_w="811" data-ori_h="433" style="width:406px;height:217px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/e1d3b18.png" alt="[圖]" /></div>
<span style="color:#00F000">安裝 Certbot 的 DNS 擴充功能</span>
Certbot 有對各家 DNS 寫了擴充功能,其中就有 Linode 的 DNS
參考 <a href="https://certbot.eff.org/instructions?ws=apache&os=centosrhel7&tab=wildcard" target="_blank" rel="nofollow">Certbot官網的DNS認證說明</a>
先設定這個才能安裝 certbot 的擴充功能
<code class="code-inline">$ sudo snap set certbot trust-plugin-with-root=ok</code>
安裝 certbot-dns-linode 擴充功能
<code class="code-inline">$ sudo snap install certbot-dns-linode</code>
新增 credentials 檔,就是 Linode API 的設定值
<code class="code-inline">$ sudo mkdir /root/.config</code>
<code class="code-inline">$ sudo vim /root/.config/linode.ini</code>
<div class="highlight"><span></span># Linode API credentials used by Certbot
dns_linode_key = {Personal Access Token}
dns_linode_version = 4
</div>
其中 {Personal Access Token} 改為之前產生的 Personal Access Token
將權限設為只限擁有者讀寫
<code class="code-inline">$ sudo chmod 600 /root/.config/linode.ini</code>
<span style="color:#00F000">執行 certbot 使用 DNS 認證申請 SSL 憑證</span>
<code class="code-inline">$ sudo certbot certonly --dns-linode --dns-linode-credentials /root/.config/linode.ini --dns-linode-propagation-seconds 120 -d disp.cc -d *.disp.cc</code>
其中 --dns-linode-credentials /root/.config/linode.ini
輸入之前建立的 credentials 檔,裡面有 Linode API 的 Personal Access Token
--dns-linode-propagation-seconds 120
代表執行後會等待120秒讓DNS更新資料後再確認,
會在DNS新增以下TXT記錄,確認後就會移除
_acme-challenge xxxxxx
-d disp.cc -d *.disp.cc 代表要認證的網域名稱
使用 DNS-01 Challenge 認證可以申請萬用字元憑證(Wildcard Certificate)
就是網域名稱的第一個字可以是任意字串
例如使用 *.disp.cc,就可以將憑證用在 www.disp.cc, knuckles.disp.cc, xxx.disp.cc
執行結果:
<div class="highlight"><span></span>Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for disp.cc and *.disp.cc
Waiting 120 seconds for DNS changes to propagate
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/disp.cc/fullchain.pem
Key is saved at: /etc/letsencrypt/live/disp.cc/privkey.pem
This certificate expires on 2023-10-07.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
</div>
產生了兩個憑證檔 fullchain.pem 與 privkey.pem
Certbot 已自動設定定期執行,不用再寫至 crontab
<code class="code-inline">$ sudo systemctl list-timers</code>
<div class="highlight"><span></span>
NEXT LEFT LAST PASSED
Mon 2023-07-10 10:11:00 CST 14h left Sun 2023-07-09 18:42:00 CST 49min ago
UNIT ACTIVATES
snap.certbot.renew.timer snap.certbot.renew.service
</div>
<span style="color:#00F000">手動設定 NodeBalancer 的 SSL憑證</span>
申請好的憑證檔就可以用來輸入 NodeBalancer 的設定
例如在 Linode 設定了一台 NodeBalancer,Label 取為 nb2
<div class="img" data-ori_w="1024" data-ori_h="643" style="width:512px;height:322px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/3920144.png" alt="[圖]" /></div>
在 NodeBalancer 的 Configuration 新增 port 443,Protocol 選擇 HTTPS
將之前產生的兩個憑證檔
/etc/letsencrypt/live/disp.cc/fullchain.pem
/etc/letsencrypt/live/disp.cc/privkey.pem
的內容複製後分別貼在 SSL Certificate 與 Private Key 輸入框中
<div class="img" data-ori_w="800" data-ori_h="799" style="width:400px;height:400px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/ecf3214.png" alt="[圖]" /></div>
然後在 Backend Nodes 使用 port 80 連結至 Linode 主機
<div class="img" data-ori_w="566" data-ori_h="269" style="width:283px;height:135px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/97a88f5.png" alt="[圖]" /></div>
完成後測試看看能否使用 https 連線
<div class="img" data-ori_w="908" data-ori_h="547" style="width:454px;height:274px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/b7824af.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Linode-cli 自動更新 NodeBalancer 的設定</span>
由於 Let's Encrypt 憑證90天就會到期,所以要能自動更新 NodeBalancer 上的憑證
參考 <a href="https://www.linode.com/docs/products/tools/cli/guides/install/" target="_blank" rel="nofollow">https://www.linode.com/docs/products/tools/cli/guides/install/</a>
安裝 Linode-cli
要先安裝 python3
<code class="code-inline">$ sudo yum install python3 python3-pip</code>
使用 pip3 安裝 linode-cli
<code class="code-inline">$ sudo pip3 install linode-cli --upgrade</code>
試試看能否執行
<code class="code-inline">$ sudo linode-cli --help</code>
若使用root執行找不到,在root的執行路徑加上 linode-cli 的軟連結
<code class="code-inline">$ sudo ln -s /usr/local/bin/linode-cli /usr/bin/linode-cli</code>
若出現 501 Protocol scheme 'https' is not supported (LWP::Protocol::https not installed)
就再安裝 perl-LWP-Protocol-https
<code class="code-inline">$ sudo yum install perl-LWP-Protocol-https</code>
第一次執行 linode-cli 要先設定
<code class="code-inline">$ sudo linode-cli configure</code>
<div class="highlight"><span></span>Welcome to the Linode CLI. This will walk you through some initial setup.
First, we need a Personal Access Token. To get one, please visit
<a href="https://cloud.linode.com/profile/tokens" target="_blank" rel="nofollow">https://cloud.linode.com/profile/tokens</a> and click
"Create a Personal Access Token". The CLI needs access to everything
on your account to work correctly.
Personal Access Token:
</div>
輸入之前產生的 Personal Access Token
接著輸入預設的區域、方案、作業系統
Default Region for operations. Choices are:
14 - ap-northeast (日本機房選這個)
Default Type of Linode to deploy. Choices are:
3 - g6-standard-2 (Shared CPU 2GB)
Default Image to deploy to new Linodes. Choices are:
9 - linode/centos7
這邊設定的內容會存在 /root/.config/linode-cli
參考 NodeBalancer 的API <a href="https://www.linode.com/docs/api/nodebalancers/" target="_blank" rel="nofollow">https://www.linode.com/docs/api/nodebalancers/</a>
先列出目前有使用的 NodeBalancers
<code class="code-inline">$ sudo linode-cli nodebalancers list --text --format id,label,region,ipv4</code>
<div class="highlight"><span></span>id label region ipv4
12345 nb2 ap-northeast 139.162.95.99
</div>
記下要更新的 nodeBalancerId 例如 12345
加 --text 代表用tab分隔資料就好,預設是會畫表格
加 --format xxx 代表列出xxx這些資料就好了
列出這個 NodeBalancer 的 Configurations
<code class="code-inline">$ sudo linode-cli nodebalancers configs-list 12345 --text --format id,port,protocol</code>
<div class="highlight"><span></span>id port protocol
4566 80 http
4567 443 https
</div>
記下要更新的 configId 例如 4567
有了 nodeBalancerId 與 configId 就可以使用 config-update 更新憑證了
使用 config-update 更新 NodeBalancer 上的憑證設定
<code class="code-inline">$ sudo linode-cli nodebalancers config-update 12345 4567 --ssl_cert /etc/letsencrypt/live/disp.cc/fullchain.pem --ssl_key /etc/letsencrypt/live/disp.cc/privkey.pem</code>
注意要用root權限才讀得到憑證檔
寫成bash檔
<code class="code-inline">$ vim /root/update_linode.sh</code>
<div class="highlight"><span></span><span class="ch">#!/bin/bash</span>
<span class="nv">nodeBalancerId</span><span class="o">=</span><span class="m">12345</span>
<span class="nv">configId</span><span class="o">=</span><span class="m">4567</span>
linode-cli<span class="w"> </span>nodebalancers<span class="w"> </span>config-update<span class="w"> </span><span class="nv">$nodeBalancerId</span><span class="w"> </span><span class="nv">$configId</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--ssl_cert<span class="w"> </span>/etc/letsencrypt/live/disp.cc/fullchain.pem<span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--ssl_key<span class="w"> </span>/etc/letsencrypt/live/disp.cc/privkey.pem
</div>
設定 crontab 定期更新憑證至 NodeBalancer
例如在每週5的18:45執行一次
<code class="code-inline">$ vim /etc/crontab</code>
45 18 * * 5 root /root/update_linode.sh
參考
<a href="https://github.com/benlabbeus/nodebalancer-ssl" target="_blank" rel="nofollow">Linode Node Balancer Auto SSL - GitHub</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2023-07-04 02:58:47 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-29 23:20:45 (台灣)</span></div></pre>
Knuckles
[CentOS7] Postfix 設定 TLS 加密郵件
http://disp.cc/b/11-erOF
2021-12-21T05:58:36+08:00
2023-11-15T13:57:41+08:00
使用Postfix寄信到Gmail時,會看到安全性被標上紅色未加密的圖示
如果想要寄出被標為有加密的郵件,要設定Postfix的TLS加密
申請SSL憑証
使用 Certbot 來幫寄件伺服器申請免費的 Let's Encrypt SSL 憑証
要先安裝 EPEL (Extra Packages for Enterprise Linux) 擴充資源庫
$ sudo yum install epel-release
安裝 openssl
$ sudo yum update ca-certificates openssl
安裝 Certbot
$ sudo yum install certbot python2-certbot-apache
使用 cerbot 申請 mail.disp.cc 的 SSL 憑証
$ sudo certbot certonly --apache -d mail.disp.cc
certonly 代表只申請憑證,不要修改 http.conf
--apache 代表網頁伺服器是使用Apache
-d mail.disp.cc 要改成自己的郵件伺服器網址
...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [CentOS7] Postfix 設定 TLS 加密郵件<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2021-12-21 Tue. 05:58:36</div><hr color="#008080" />───────────────────
使用Postfix寄信到Gmail時,會看到安全性被標上紅色未加密的圖示
<div class="img" data-ori_w="670" data-ori_h="380" style="width:335px;height:190px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/88abe9f.png" alt="[圖]" /></div>
如果想要寄出被標為有加密的郵件,要設定Postfix的TLS加密
<span style="color:#00F000">申請SSL憑証</span>
使用 Certbot 來幫寄件伺服器申請免費的 Let's Encrypt SSL 憑証
要先安裝 EPEL (Extra Packages for Enterprise Linux) 擴充資源庫
<code class="code-inline">$ sudo yum install epel-release</code>
安裝 openssl
<code class="code-inline">$ sudo yum update ca-certificates openssl</code>
安裝 Certbot
<code class="code-inline">$ sudo yum install certbot python2-certbot-apache</code>
使用 cerbot 申請 mail.disp.cc 的 SSL 憑証
<code class="code-inline">$ sudo certbot certonly --apache -d <span style="color:#008080">mail.disp.cc</span></code>
certonly 代表只申請憑證,不要修改 http.conf
--apache 代表網頁伺服器是使用Apache
-d <span style="color:#008080">mail.disp.cc</span> 要改成自己的郵件伺服器網址
執行後會要你輸入E-mail做為憑證到期通知,按c略過的話會停止認證
接著會要你同意使用條款,輸入 a 同意
接著問你是否要接收相關新聞的 E-mail,輸入 n 不同意
申請成功的話會產生兩個檔
憑証: /etc/letsencrypt/live/mail.disp.cc/fullchain.pem
私鑰: /etc/letsencrypt/live/mail.disp.cc/privkey.pem
<span style="color:#00F000">設定 Postfix</span>
修改 Postfix 設定檔
<code class="code-inline">$ sudo vim /etc/postfix/main.cf</code>
最後面加上
<div class="highlight"># 設定 TLS
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
# 以下兩個檔案要改成上面產生的私鑰和憑証的檔案
smtpd_tls_key_file = /etc/letsencrypt/live/mail.disp.cc/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.disp.cc/fullchain.pem
smtpd_tls_CAfile = $smtpd_tls_cert_file
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
smtpd_tls_auth_only = yes
# 以上設定只有讓使用者跟Postfix之間有加密,
# 要再加上以下設定讓信件轉送至其他郵件伺服器時也有加密
smtp_use_tls = yes
smtp_tls_note_starttls_offer = yes
</div>
重載 Postfix 設定
<code class="code-inline">$ sudo systemctl reload postfix</code>
<span style="color:#00F000">自動更新憑証</span>
Let's Encrypt 的憑証期限只有三個月,
設定每個月自動檢查一次憑証,有效期低於一個月才會更新憑證
<code class="code-inline">$ sudo vim /etc/crontab</code>
<div class="highlight"># 加上這行,每月某個時間檢查,例如每月 4 日的 3:12
12 3 4 * * root certbot renew
# 有更新的話要重載一次 Postfix
22 3 4 * * root systemctl reload postfix
</div>
使用 checktls.com 可以檢查 TLS 設定是否正常
<a href="https://www.checktls.com/TestReceiver" target="_blank" rel="nofollow">https://www.checktls.com/TestReceiver</a>
<div class="img" data-ori_w="1918" data-ori_h="549" style="width:959px;height:275px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/885455e.png" alt="[圖]" /></div>
設定成功的話寄信到Gmail就會顯示有TLS加密了
<div class="img" data-ori_w="594" data-ori_h="304" style="width:297px;height:152px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/e8216dc.png" alt="[圖]" /></div>
參考:
<a href="https://ezbox.idv.tw/63/lets-encrypt-ssl-smtp-tls-smpts/" target="_blank" rel="nofollow">https://ezbox.idv.tw/63/lets-encrypt-ssl-smtp-tls-smpts/</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2021-12-21 05:58:36 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-15 13:57:41 (台灣)</span></div></pre>
Knuckles
[CentOS7] Postfix 設定 SPF DKIM DMARC 防止被假冒寄信
http://disp.cc/b/11-eqjN
2021-12-15T03:34:02+08:00
2023-11-15T14:00:06+08:00
SPF DKIM DMARC 是三個用來驗証E-mail寄件者是否為假冒的方法
三個都需要在DNS設定加上TXT記錄
SPF最簡單就是記錄寄件伺服器的網域名稱有哪些,
例如我用 mail.disp.cc 寄出一封寄件者為 knuckles@disp.cc 的信時
收件伺服器若有支援 SPF,就會檢查 disp.cc 的DNS上是否有設定SPF允許 mail.disp.cc 寄信
DKIM比較嚴格要用金鑰來驗証,要在DNS上放上一組公鑰
然後寄件伺服器要設定 DKIM,在寄出的信用私鑰加上簽名
收件伺服器若有支援 DKIM,就會用DNS上的公鑰檢查簽名是否正確
DMARC就是設定讓收件伺服器知道SPF和DKIM不通過時要怎麼處理
可以略過不管、當垃圾信或退信,然後將處理的結果寄到某個信箱
先寄一封信到Gmail信箱,使用Gmail看原始郵件內容可以看到是否有通過 SPF 和 DKIM
以下設定使用 CentOS7,mail server使用 postfix
DNS設定使用 Linode DNS Manager
設定 SPF (Sender Policy Framework)
只要在DNS ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [CentOS7] Postfix 設定 SPF DKIM DMARC 防止被假冒寄信<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2021-12-15 Wed. 03:33:57</div><hr color="#008080" />───────────────────
SPF DKIM DMARC 是三個用來驗証E-mail寄件者是否為假冒的方法
三個都需要在DNS設定加上TXT記錄
SPF最簡單就是記錄寄件伺服器的網域名稱有哪些,
例如我用 mail.disp.cc 寄出一封寄件者為 knuckles@disp.cc 的信時
收件伺服器若有支援 SPF,就會檢查 disp.cc 的DNS上是否有設定SPF允許 mail.disp.cc 寄信
DKIM比較嚴格要用金鑰來驗証,要在DNS上放上一組公鑰
然後寄件伺服器要設定 DKIM,在寄出的信用私鑰加上簽名
收件伺服器若有支援 DKIM,就會用DNS上的公鑰檢查簽名是否正確
DMARC就是設定讓收件伺服器知道SPF和DKIM不通過時要怎麼處理
可以略過不管、當垃圾信或退信,然後將處理的結果寄到某個信箱
先寄一封信到Gmail信箱,使用Gmail看原始郵件內容可以看到是否有通過 SPF 和 DKIM
<div class="img" data-ori_w="1781" data-ori_h="766" style="width:891px;height:383px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/d7a0471.png" alt="[圖]" /></div>
以下設定使用 CentOS7,mail server使用 postfix
DNS設定使用 Linode DNS Manager
<span style="color:#00F000">設定 SPF (Sender Policy Framework)</span>
只要在DNS設定加個TXT記錄
可以使用mxtoolbox的產生器
<a href="https://mxtoolbox.com/SPFRecordGenerator.aspx" target="_blank" rel="nofollow">https://mxtoolbox.com/SPFRecordGenerator.aspx</a>
將會用來寄信的伺服器都填上去
例如
<div class="img" data-ori_w="1024" data-ori_h="1106" style="width:512px;height:553px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/daa0f10.png" alt="[圖]" /></div>
如果點「Finalize Record」,會將設定字串簡化
例如產生的設定字串為 v=spf1 a:mail.disp.cc ~all
將產生的字串寫入DNS的TXT
例如DNS是用Linode的話
<div class="img" data-ori_w="1024" data-ori_h="374" style="width:512px;height:187px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/a301381.png" alt="[圖]" /></div>
<span style="color:#00F000">設定 DKIM (DomainKeys Identified Mail)</span>
需要先安裝 EPEL 套件庫
<code class="code-inline">$ sudo yum install -y epel-release</code>
安裝 DKIM
<code class="code-inline">$ sudo yum install opendkim</code>
建立用來放金鑰檔的資料夾
<code class="code-inline">$ sudo mkdir /etc/opendkim/keys/<span style="color:#008080">disp.cc</span></code>
<span style="color:#008080">disp.cc</span>換成自己的網域名
產生金鑰檔
<code class="code-inline">$ sudo opendkim-genkey --bits=2048 --selector=default --domain=<span style="color:#008080">disp.cc</span> --directory=/etc/opendkim/keys/<span style="color:#008080">disp.cc</span> -v</code>
或是使用簡寫
<code class="code-inline">$ sudo opendkim-genkey -b 2048 -s default -d <span style="color:#008080">disp.cc</span> -D /etc/opendkim/keys/<span style="color:#008080">disp.cc</span> -v</code>
其中<span style="color:#008080">disp.cc</span>要換成自己的網址
一個網址可指定多個key,selector代表要用哪一個,使用預設值default即可
-v 代表顯示結果
會在 /etc/opendkim/keys/<span style="color:#008080">disp.cc</span> 產生兩個檔案
default.private 私鑰
default.txt 要用來寫入DNS的公鑰
更改金鑰檔的權限
<code class="code-inline">$ sudo chown opendkim:opendkim -R /etc/opendkim/keys/*</code>
<code class="code-inline">$ sudo chmod 640 -R /etc/opendkim/keys/<span style="color:#008080">disp.cc</span>/*</code>
編輯 DKIM 的設定檔 /etc/opendkim.conf
<code class="code-inline">$ sudo vim /etc/opendkim.conf</code>
<div class="highlight">PidFile /var/run/opendkim/opendkim.pid
#Mode v
#Mode預設只會驗証(verify)要加上s才會將寄的信加上簽名(sign)
Mode sv
Syslog yes
SyslogSuccess yes
LogWhy yes
#UserID opendkim:opendkim
UserID opendkim:postfix
#Socket inet:8891@localhost
#Socket local:/var/run/opendkim/opendkim.sock
Socket local:/var/spool/postfix/var/run/opendkim/opendkim.sock
#Umask 002
UMask 000
#SendReports yes
#是否要回寄錯誤訊息給驗証失敗的信
SendReports no
SoftwareHeader yes
#Canonicalization relaxed/relaxed
Canonicalization relaxed/simple
# Domain example.com
# 只有要設定一個網域的話,改成自己的網域名稱
Domain disp.cc
Selector default
#MinimumKeyBits 1024
MinimumKeyBits 2048
# 只有產生一個金鑰檔的話,改成金鑰檔的路徑
KeyFile /etc/opendkim/keys/disp.cc/default.private
#KeyTable /etc/opendkim/KeyTable
# 有多個網域要設定的話,使用KeyTable與SigningTable
KeyTable /etc/opendkim/KeyTable
#SigningTable refile:/etc/opendkim/SigningTable
SigningTable refile:/etc/opendkim/SigningTable
#InternalHosts refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts
OversignHeaders From
</div>
有使用 KeyTable 和 SigningTable 的話
編輯 DKIM 的設定檔 KeyTable
<code class="code-inline">$ sudo vim /etc/opendkim/KeyTable</code>
加上
<div class="highlight">default._domainkey.<span style="color:#008080">disp.cc</span> <span style="color:#008080">disp.cc</span>:default:/etc/opendkim/keys/<span style="color:#008080">disp.cc</span>/default.private
</div>
編輯 DKIM 的設定檔 SigningTable
<code class="code-inline">$ sudo vim /etc/opendkim/SigningTable</code>
加上
<div class="highlight">*@<span style="color:#008080">disp.cc</span> default._domainkey.<span style="color:#008080">disp.cc</span>
</div>
編輯 DKIM 的設定檔 TrustedHosts
<code class="code-inline">$ sudo vim /etc/opendkim/TrustedHosts</code>
加上
<div class="highlight">*.<span style="color:#008080">disp.cc</span>
</div>
重啟 opendkim
<code class="code-inline">$ sudo systemctl restart opendkim</code>
設定開機啟動 opendkim
<code class="code-inline">$ sudo systemctl enable opendkim</code>
確認 opendkim 執行狀態
<code class="code-inline">$ sudo systemctl status opendkim</code>
編輯 /etc/postfix/main.cf
<code class="code-inline">$ sudo vim /etc/postfix/main.cf</code>
最後面加上
<div class="highlight">### OpenDKIM
milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:/var/run/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
</div>
重啟 postfix
<code class="code-inline">$ sudo systemctl restart postfix</code>
確認 postfix 執行狀態
<code class="code-inline">$ sudo systemctl status postfix</code>
將產生的金鑰寫入DNS的TXT
讀取 /etc/opendkim/keys/disp.cc/default.txt
內容會像這樣:
default._domainkey IN TXT ( "v=DKIM1; k=rsa; t=s; s=email; "
"p=MII…ZKA"
"zhq…QAB" ) ; ----- DKIM key default for disp.cc
將被分開的字串合併後,要寫入的內容就是
Hostname輸入:「default._domainkey」
Value輸入:「v=DKIM1; k=rsa; t=s; s=email; p=MII…ZKAzhq…QAB」
例如DNS是用Linode的話
<div class="img" data-ori_w="2048" data-ori_h="612" style="width:1024px;height:306px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/4c1335c.png" alt="[圖]" /></div>
寄一封信測試,檢查 /var/log/maillog 看看有沒有加上 DKIM
opendkim[16478]: 902682592: DKIM-Signature field added (s=default, d=disp.cc)
若 maillog 顯示
postfix/smtpd[16736]: warning: connect to Milter service unix:/var/run/opendkim/opendkim.sock: Permission denied
解決方法參考: <a href="https://unix.stackexchange.com/questions/74477/postfix-smtpd-warning-connect-to-milter-service-unix-var-run-opendkim-opendki" target="_blank" rel="nofollow">https://unix.stackexchange.com/quest...-unix-var-run-opendkim-opendki</a>
將 /var/run/opendkim/opendkim.sock 改放到 /var/spool/postfix/ 裡
先手動新增資料夾
<code class="code-inline">$ sudo mkdir -p /var/spool/postfix/var/run/opendkim</code>
<code class="code-inline">$ sudo chown opendkim:opendkim /var/spool/postfix/var/run/opendkim</code>
<code class="code-inline">$ vim /etc/opendkim.conf</code>
將 Socket local:/var/run/opendkim/opendkim.sock
改成 Socket local:<span style="color:#00F000">/var/spool/postfix</span>/var/run/opendkim/opendkim.sock
<code class="code-inline">$ vim /etc/postfix/main.cf</code>
將 smtpd_milters = unix:/var/run/opendkim/opendkim.sock
改成 smtpd_milters = unix:var/run/opendkim/opendkim.sock
(var前面的/拿掉變成相對路徑)
重啟 opendkim 與 postfix
<code class="code-inline">$ sudo systemctl restart opendkim</code>
<code class="code-inline">$ sudo systemctl restart postfix</code>
<span style="color:#00F000">設定 DMARC (Domain-based Message Authentication, Reporting & Conformance)</span>
可以使用mxtoolbox的產生器
<a href="https://mxtoolbox.com/DMARCRecordGenerator.aspx" target="_blank" rel="nofollow">https://mxtoolbox.com/DMARCRecordGenerator.aspx</a>
<div class="img" data-ori_w="2048" data-ori_h="1159" style="width:1024px;height:580px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/12b71a9.png" alt="[圖]" /></div>
第一項設定「none」代表略過不處理,若還不確定SPF和DKIM有沒有設定好的話可以先設這個
設定「Quarantine」代表將沒通過SPF或DKIM的信設為垃圾信
設定「Reject」代表將信退回
第二和第三項設定處理的報告要寄到哪個信箱
因為每處理了幾封信就會寄一個報告出來,所以最好另外用個專門的信箱來收這個
將產生的設定字串填入DNS的TXT
Hostname填入「_DMARC」
Value填入「v=DMARC1; p=quarantine; rua=mailto:xxx@xxx; ruf=mailto:xxx@xxx; fo=1」
例如DNS是用Linode的話
<div class="img" data-ori_w="2048" data-ori_h="660" style="width:1024px;height:330px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/8259785.png" alt="[圖]" /></div>
設定成功的話,寄信到Gmail看原始郵件,就可以看到三項都是PASS了
<div class="img" data-ori_w="2048" data-ori_h="843" style="width:1024px;height:422px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/83ef59e.png" alt="[圖]" /></div>
收到的DMARC報告會是一個xml檔,可以上傳到這邊來看
<a href="https://mxtoolbox.com/DmarcReportAnalyzer.aspx" target="_blank" rel="nofollow">https://mxtoolbox.com/DmarcReportAnalyzer.aspx</a>
<div class="img" data-ori_w="1774" data-ori_h="577" style="width:887px;height:289px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/9f9b539.png" alt="[圖]" /></div>
參考:
<a href="https://unixwise.xyz/wordpress/2019/06/%E5%BB%BA%E8%A8%AD-postfix-spf-dkim-dmarc/" target="_blank" rel="nofollow">https://unixwise.xyz/wordpress/2019/06/建設-postfix-spf-dkim-dmarc/</a>
<a href="https://idolsgate.com/blog/configure-spf-dkim-dmarc-tls-postfix-centos_7/" target="_blank" rel="nofollow">https://idolsgate.com/blog/configure-spf-dkim-dmarc-tls-postfix-centos_7/</a>
<a href="https://tech-blog.cymetrics.io/posts/crystal/email-sec-settings-dkimdmarc/" target="_blank" rel="nofollow">關於 email security 的大小事 — 設定篇 DKIM、DMARC</a>
<a href="https://easydmarc.com/blog/how-to-configure-dkim-opendkim-with-postfix/" target="_blank" rel="nofollow">https://easydmarc.com/blog/how-to-configure-dkim-opendkim-with-postfix/</a>
<a href="https://n.sfs.tw/content/index/15784" target="_blank" rel="nofollow">[Centos7 8] postfix+DKIM 設定</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2021-12-15 03:34:02 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-15 14:00:05 (台灣)</span></div></pre>
Knuckles
[WAMP] 在Windows10上架Apache+MySQL+PHP
http://disp.cc/b/11-dB19
2021-05-26T06:13:54+08:00
2023-09-18T19:06:50+08:00
本文安裝的作業系統為 Windows 10 64bit
至 wamp 官網下載最新版
https://www.wampserver.com/en/
目前新版本為 3.2
下載 wampserver3.2.3_x64.exe,安裝時會出現這個注意事項
安裝 wamp 前,要先安裝 Visual C++ Packages
可以在這邊下載 https://wampserver.aviatechno.net/?lang=en
下載後解壓縮,依序安裝所有的執行檔,x64和x86都要裝
或是點下面的 VisualCppRedist AIO All Releases
下載最新的 VisualCppRedist_AIO_x86_x64.exe 可一次安裝所有 Packages
都裝好後就可以開始安裝 wampserver3.2.3_x64.exe
如果要升級的話記得先備份資料庫
要先移除舊版的 wamp 才能安裝新版的
安裝好後執行,點右下角的圖示按滑鼠右鍵,點「Language」有 chinese_trad 的話可將語言改為繁體中文
NOTE: 若沒有 chinese_trad 的話不要選 ch ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [WAMP] 在Windows10上架Apache+MySQL+PHP <br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2021-05-26 Wed. 06:13:54</div><hr color="#008080" />───────────────────
本文安裝的作業系統為 Windows 10 64bit
至 wamp 官網下載最新版
<a href="https://www.wampserver.com/en/" target="_blank" rel="nofollow">https://www.wampserver.com/en/</a>
<div class="img" data-ori_w="1024" data-ori_h="528" style="width:512px;height:264px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/3e3d248.png" alt="[圖]" /></div>
目前新版本為 3.2
下載 wampserver3.2.3_x64.exe,安裝時會出現這個注意事項
<div class="img" data-ori_w="1024" data-ori_h="757" style="width:512px;height:379px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/f1340c9.png" alt="[圖]" /></div>
安裝 wamp 前,要先安裝 Visual C++ Packages
可以在這邊下載 <a href="https://wampserver.aviatechno.net/?lang=en" target="_blank" rel="nofollow">https://wampserver.aviatechno.net/?lang=en</a>
<div class="img" data-ori_w="1024" data-ori_h="202" style="width:512px;height:101px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/4e0934e.png" alt="[圖]" /></div>
下載後解壓縮,依序安裝所有的執行檔,x64和x86都要裝
<div class="img" data-ori_w="458" data-ori_h="604" style="width:229px;height:302px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/266df57.png" alt="[圖]" /></div>
或是點下面的 <a href="https://github.com/abbodi1406/vcredist/releases" target="_blank" rel="nofollow">VisualCppRedist AIO All Releases</a>
下載最新的 VisualCppRedist_AIO_x86_x64.exe 可一次安裝所有 Packages
都裝好後就可以開始安裝 wampserver3.2.3_x64.exe
如果要升級的話記得先備份資料庫
要先移除舊版的 wamp 才能安裝新版的
安裝好後執行,點右下角的圖示按滑鼠右鍵,點「Language」有 chinese_trad 的話可將語言改為繁體中文
NOTE: 若沒有 chinese_trad 的話不要選 chinese,不然可能會因為設定檔有簡中出現以下錯誤無法開啟,只能重新安裝
[EParseError] Mismatched or misplaced quotes on parameter "PromptCaption"
可自行修改 E:\wamp33\lang\chinese.lang
將 $file_charset = 'GB2312'; 這行刪除後,將下面的簡體中文改為繁體中文
PHP的版本預設使用7,要改成舊版5.6的話
點wamp圖示選php,Version,5.6
<div class="img" data-ori_w="925" data-ori_h="582" style="width:463px;height:291px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/eb096b8.png" alt="[圖]" /></div>
但預設安裝的 phpmyadmin 5.1 不支援 php5.6
所以要改裝舊版的 phpmyadmin 4.9.7
在 <a href="https://wampserver.aviatechno.net/?lang=en" target="_blank" rel="nofollow">https://wampserver.aviatechno.net/?lang=en</a>
<div class="img" data-ori_w="918" data-ori_h="410" style="width:459px;height:205px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/4a3c2eb.png" alt="[圖]" /></div>
下載 phpmyadmin 4.9.7 下載安裝即可
修改 php.ini
點W圖示 → PHP → php.ini
;修改時區
;date.timezone = UTC
date.timezone = Asia/Taipei
wamp 的 php 預設會啟用 xdebug
會將 php 產生的錯誤訊息用 html 的 table 顯示出來
如果不要的話,修改 php.ini
將 zend_extension = "e:/wamp/bin/php/php5.4.9/zend_ext/php_xdebug-2.2.1-5.4-vc9.dll"
註解掉
;zend_extension = "e:/wamp/bin/php/php5.4.9/zend_ext/php_xdebug-2.2.1-5.4-vc9.dll"
想要預設使用 mariaDB 的話,點wamp圖示右鍵,tool
「Invert Default DBMS MariaDB <==> MySQL」
<div class="img" data-ori_w="1024" data-ori_h="1236" style="width:512px;height:618px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/21/85b9864.png" alt="[圖]" /></div>
修改mysql的root密碼
一開始的 root 密碼是空的
要進 phpmyadmin <a href="http://localhost/phpmyadmin" target="_blank" rel="nofollow">http://localhost/phpmyadmin</a>
使用 root 登入,密碼不用輸入
選「使用者帳號」,新增一個有所有權限的使用者,並設定密碼
主機名稱不要用任意,選localhost
使用設定的帳號密碼重新登入 phpmyadmin
確定此帳號的全域權限為 ALL PRIVILEGES
將沒有設定密碼的 root 帳號刪除或設定密碼
將登入主機為「任意」的帳號刪除
若資料庫讀出來的資料,中文變成亂碼
在 phpmyadmin 輸入SQL指令 show variables like '%character%'
檢查是否有 latin1
點右下角W圖示,MariaDB/my.ini
在 [client] 裡加上
default-character-set=utf8
在 [wampmariadb64] 裡加上
init_connect = 'SET NAMES utf8' (或是 init_connect = 'SET NAMES utf8mb4')
在 [mysqld] 裡加上
character-set-server = utf8
若使用 file_get_contents() 讀取 https 的網頁時會出問題
=>要開啟 ssl 模組
wamp圖示/Apache/Apache模組/ssl_module
○ 使用 VirtualHost
例如使用 disp.localhost 連到 E:\wamp\www\disp 的話
修改 httpd-vhosts.conf 加上
<VirtualHost *:80>
ServerName disp.localhost
ServerAlias disp.localhost
DocumentRoot "${INSTALL_DIR}/www/disp"
<Directory "${INSTALL_DIR}/www/disp/">
Options +Indexes +Includes +FollowSymLinks -MultiViews
AllowOverride All
Require local
</Directory>
</VirtualHost>
或是點 Wamp 圖示,選「Your VirtualHosts」->「VirtualHosts Management」
兩個紅框分別輸入輸入 Name of the Virtual Host:「disp.localhost」
Complete absolute path of the VirtualHost folder:「E:\wamp\www\disp」
然後點「Start the creation/modification of the VirtualHost」即可
□ 問題解決記錄
- 有些頁面用 rewrite 失效,例如:
RewriteRule ^main$ index.php?page=main
使用 <a href="http://localhost/xxx/main" target="_blank" rel="nofollow">http://localhost/xxx/main</a> 會出現 404 Not Found
因為預設開了 MultiViews 多國語言設定,如果有 main.xxx.php 網頁的話,rewrite就會失效
要修改 httpd-vhosts.conf
將 Options +Indexes +Includes +FollowSymLinks +MultiViews
改為 Options +Indexes +Includes +FollowSymLinks -MultiViews
- 顯示 Warning E:/wamp or PHP in PATH
<div class="highlight"><span></span>Warning: There is Wampserver path (E:/wamp)
into Windows PATH environnement variable: (E:\wamp\bin\php\php8.0.26)
Warning: It seems that a PHP installation is declared in the environment variable PATH
E:\wamp\bin\php\php8.0.26
Wampserver does not use, modify or require the PATH environment variable.
Using a PATH on Wampserver or PHP version
may be detrimental to the proper functioning of Wampserver.
</div>
代表系統的執行路徑上有加了一個 PHP 的執行路徑
這可能會跟 Wamp 使用的 PHP 版本不相符,請自行修改成適合的版本
確定沒問題的話,可以對 Wamp 圖示按右鍵,點選
「Wamp Settings」->「Caution: Risky! Only for experts」->「Do not verify PATH」
將驚告訊息關掉
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2021-05-26 06:13:54 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-09-18 19:06:49 (台灣)</span></div></pre>
Knuckles
[CentOS7] Apache 使用 Certbot 申請 Let's Encrypt 的SSL憑證
http://disp.cc/b/11-czOq
2020-07-29T05:22:23+08:00
2023-12-26T02:55:26+08:00
Let's Encrypt 是免費的網頁 SSL 憑證
每三個月要重新認證一次
使用 Certbot 可以簡化認證流程,也可以設定自動重新認證
參考官網說明 https://certbot.eff.org/instructions?ws=apache&os=centosrhel7&tab=standard
要先安裝 EPEL (Extra Packages for Enterprise Linux) 擴充資源庫
$ sudo yum install epel-release
安裝 apache 的 SSL 模組,重啟 apache
$ sudo yum install mod_ssl
$ sudo systemctl restart httpd
* 使用 yum 的安裝方法 (已不建議使用)
安裝 Certbot
$ sudo yum install certbot python2-certbot-apache
相依的套件中會安裝 apache 的 mod_ssl
使用通用套件管理器 snap 的安裝方法 (官網建議使用)
安裝 snapd,參考 snap 官 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [CentOS7] Apache使用Certbot申請Let's Encrypt的SSL憑證<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2020-07-29 Wed. 05:22:11</div><hr color="#008080" />───────────────────
<div class="img" data-ori_w="640" data-ori_h="640" style="width:320px;height:320px"><img style="max-width:100%;" data-ratio="2" src="https://i4.disp.cc/u/23/8c2b7d2.png" alt="[圖]" /></div>
Let's Encrypt 是免費的網頁 SSL 憑證
每三個月要重新認證一次
使用 Certbot 可以簡化認證流程,也可以設定自動重新認證
參考官網說明 <a href="https://certbot.eff.org/instructions?ws=apache&os=centosrhel7&tab=standard" target="_blank" rel="nofollow">https://certbot.eff.org/instructions?ws=apache&os=centosrhel7&tab=standard</a>
要先安裝 EPEL (Extra Packages for Enterprise Linux) 擴充資源庫
<code class="code-inline">$ sudo yum install epel-release</code>
安裝 apache 的 SSL 模組,重啟 apache
<code class="code-inline">$ sudo yum install mod_ssl</code>
<code class="code-inline">$ sudo systemctl restart httpd</code>
* 使用 yum 的安裝方法 (已不建議使用)
<span style="color:#808080">安裝 Certbot
$ sudo yum install certbot python2-certbot-apache
相依的套件中會安裝 apache 的 mod_ssl</span>
使用通用套件管理器 snap 的安裝方法 (官網建議使用)
安裝 snapd,參考 <a href="https://snapcraft.io/docs/installing-snap-on-centos" target="_blank" rel="nofollow">snap 官網說明</a>
<code class="code-inline">$ sudo yum install snapd</code>
設定 snapd 為開機執行
<code class="code-inline">$ sudo systemctl enable --now snapd.socket</code>
建立一個 /snap 軟連結
<code class="code-inline">$ sudo ln -s /var/lib/snapd/snap /snap</code>
確認 snap 已更新至最新版
<code class="code-inline">$ sudo snap install core</code>
<code class="code-inline">$ sudo snap refresh core</code>
出現以下錯誤訊息的話,過一會再執行就好
error: too early for operation, device not yet seeded or device model not acknowledged
若之前有用 yum 安裝過 certbot 的話,要先移除
<code class="code-inline">$ sudo yum remove certbot</code>
使用 snap 安裝 certbot
<code class="code-inline">$ sudo snap install --classic certbot</code>
建立一個軟連結確保 certbot 可執行
<code class="code-inline">$ sudo ln -s /snap/bin/certbot /usr/bin/certbot</code>
使用 Certbot 申請 Let's Encrypt 的 SSL 憑證
<code class="code-inline">$ certbot certonly --apache -w /var/www/<span style="color:#008080">xxx</span>/ -d <span style="color:#008080">xxx.disp.cc</span></code>
其中:
certonly 代表只申請憑證,不要自動修改 http.conf
--apache 代表網頁伺服器是使用Apache
-w /var/www/<span style="color:#008080">xxx</span>/ 改成自己網站的根目錄
-d <span style="color:#008080">xxx.disp.cc</span> 改成自己網站的網域名,可以設定多個,例如 -d disp.cc -d www.disp.cc
執行後會要你輸入E-mail做為憑證到期通知,按c略過的話會停止認證
接著會要你同意使用條款,輸入 y 同意
接著問你是否要接收相關新聞的 E-mail,若不想要的話,可輸入 n 不同意
預設會使用 <a href="https://letsencrypt.org/zh-tw/docs/challenge-types/#http-01-%E8%80%83%E9%A9%97" target="_blank" rel="nofollow">HTTP-01 Challenge</a> 的方式做認證
執行時程式會在網站目錄下新增用來確認的檔案,例如
/var/www/<span style="color:#008080">xxx</span>/.well-known/acme-challenge/xxxxx
然後使用 Let's Encrypt 的主機連至
<a href="http://xxx.disp.cc/.well-known/acme-challenge/xxxxx" target="_blank" rel="nofollow">http://xxx.disp.cc/.well-known/acme-challenge/xxxxx</a>
看看有沒有這個檔案,有的話就通過了,通過後就會刪除用來確認的檔案
NOTE: 如果想要使用萬用字元憑證(*.disp.cc),要使用 <a href="https://letsencrypt.org/zh-tw/docs/challenge-types/#dns-01-%E8%80%83%E9%A9%97" target="_blank" rel="nofollow">DNS-01 Challenge</a> 認證,可參考這篇 <a href="https://disp.cc/b/KnucklesNote/gfCN" target="_blank" rel="nofollow">[Linode] NodeBalancer 使用 Let's Encrypt SSL憑證</a>
認證成功的話會出現以下訊息:
<div class="highlight"><span></span>Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/xxx.disp.cc/fullchain.pem
Key is saved at: /etc/letsencrypt/live/xxx.disp.cc/privkey.pem
This certificate expires on 2020-10-26.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
</div>
產生的憑證檔放在 /etc/letsencrypt/live/xxx.disp.cc/
Certbot 已設定了排程自動更新憑證檔
修改 apache 的 virtual host 設定檔
<code class="code-inline">$ sudo vim /etc/httpd/conf.d/vhost.conf</code>
將原本 xxx.disp.cc 的設定,例如為
<div class="highlight"><span></span><span class="nt"><VirtualHost</span><span class="w"> </span><span class="s">*:80</span><span class="nt">></span>
<span class="w"> </span><span class="nb">ServerName</span><span class="w"> </span>xxx.disp.cc
<span class="w"> </span><span class="nb">DocumentRoot</span><span class="w"> </span><span class="sx">/var/www/xxx</span>
<span class="w"> </span><span class="c"># …</span>
<span class="nt"></VirtualHost></span>
</div>
複製一份,改成
<div class="highlight"><span></span><span class="nt"><VirtualHost</span><span class="w"> </span><span class="s">*:443</span><span class="nt">></span>
<span class="w"> </span><span class="c"># 插入以下四行,xxx.disp.cc 改成自己的網域名</span>
<span class="w"> </span><span class="nb">Include</span><span class="w"> </span><span class="sx">/etc/letsencrypt/options-ssl-apache.conf</span>
<span class="w"> </span><span class="nb">SSLCertificateFile</span><span class="w"> </span><span class="sx">/etc/letsencrypt/live/</span><span style="color:#008080">xxx.disp.cc</span>/cert.pem
<span class="w"> </span><span class="nb">SSLCertificateKeyFile</span><span class="w"> </span><span class="sx">/etc/letsencrypt/live/</span><span style="color:#008080">xxx.disp.cc</span>/privkey.pem
<span class="w"> </span><span class="nb">SSLCertificateChainFile</span><span class="w"> </span><span class="sx">/etc/letsencrypt/live/</span><span style="color:#008080">xxx.disp.cc</span>/fullchain.pem
<span class="w"> </span><span class="c"># 在 *:80 的設定這邊也要再寫一遍</span>
<span class="w"> </span><span class="nb">ServerName</span><span class="w"> </span>xxx.disp.cc
<span class="w"> </span><span class="nb">DocumentRoot</span><span class="w"> </span><span class="sx">/var/www/xxx</span>
<span class="w"> </span><span class="c"># … </span>
<span class="nt"></VirtualHost></span>
</div>
測試設定檔是否正確
<code class="code-inline">$ sudo httpd -t</code>
重載 Apache 設定
<code class="code-inline">$ sudo systemctl reload httpd</code>
防火牆設定,使用 firewalld 的話,開啟 https 服務
<code class="code-inline">$ sudo firewall-cmd --zone=public --add-service=https --permanent</code>
使用 iptables 的話,修改設定檔打開 443 port,例如
<code class="code-inline">iptables -A INPUT -p TCP -i eth0 --dport 443 -j ACCEPT # HTTPS</code>
列出所有憑證及到期日
<code class="code-inline">$ sudo certbot certificates</code>
<div class="highlight"><span></span>Found the following certs:
Certificate Name: xxx.disp.cc
Serial Number: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Key Type: ECDSA
Domains: xxx.disp.cc
Expiry Date: 2020-10-26 19:24:03+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/xxx.disp.cc/fullchain.pem
Private Key Path: /etc/letsencrypt/live/xxx.disp.cc/privkey.pem
</div>
測試能否更新憑證
<code class="code-inline">$ sudo certbot renew --dry-run</code>
Certbot 已自動設定定期執行,不用再寫至 crontab
<code class="code-inline">$ sudo systemctl list-timers</code>
<div class="highlight"><span></span>NEXT LEFT LAST PASSED UNIT ACTIVATES
Sat 2023-09-30 21:27:00 CST 6h left - - snap.certbot.renew.timer snap.certbot.renew.service
</div>
* 若沒有自動排程的話,再設定排程定期更新
每個月自動檢查一次憑證,有效期低於一個月才會更新憑證
<code class="code-inline">$ sudo vim /etc/crontab</code>
加上這行,每月某個時間檢查,例如每月 4 日的 3:12
<code class="code-inline">12 3 4 * * root certbot renew</code>
○ 刪除SSL憑證
如果某個網址不需要使用了要刪掉設定的話
如果還沒到期的話,要先撤銷網域 SSL 憑證
<code class="code-inline">$ sudo certbot revoke --cert-path /etc/letsencrypt/live/<span style="color:#008080">xxx.disp.cc</span>/cert.pem</code>
撤銷後會再詢問是否要刪除憑證檔
若apache的設定還有用到這些憑證檔的話,刪除可能會產生問題
確定已經沒有使用到的話再刪除
只要刪除SSL憑證檔與相關設定的話,使用
<code class="code-inline">$ sudo certbot delete --cert-name <span style="color:#008080">xxx.disp.cc</span></code>
參考:
<a href="https://www.digit-seed.com/centos7-certbot-lets_encrypt_ssl/" target="_blank" rel="nofollow">安裝Certbot工具,與Let's Encrypt申請SSL金鑰憑證,自動更新SSL憑證</a>
相關文章:
<a href="https://disp.cc/b/KnucklesNote/9SKl" target="_blank" rel="nofollow">[CentOS7] Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/gvK9" target="_blank" rel="nofollow">[RockyLinux9] 網頁伺服器 Apache 安裝與設定 - KnucklesNote板 - Disp BBS</a>
<a href="https://disp.cc/b/KnucklesNote/gfCN" target="_blank" rel="nofollow">[Linode] NodeBalancer 使用 Let's Encrypt SSL憑證 - KnucklesNote板 - Disp BBS</a>
錯誤修正記錄:
出現 SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
或 TypeError: __str__ returned non-string (type Error)
→ 更新 openssl 看看
<code class="code-inline">$ sudo yum update ca-certificates openssl</code>
出現 ImportError: 'pyOpenSSL' module missing required functionality. Try upgrading to v0.14 or newer.
→ 參考 <a href="https://medium.com/@getpagespeed/fix-importerror-pyopenssl-module-missing-required-functionality-e1c514797204" target="_blank" rel="nofollow">[Fix] ImportError: ‘pyOpenSSL’ module missing required functionality. Try upgrading to v0.14 or newer.</a>
移除 python 的一些套件,改用 yum 安裝的
<code class="code-inline">$ sudo pip uninstall requests</code>
<code class="code-inline">$ sudo yum reinstall python-requests</code>
<code class="code-inline">$ sudo pip uninstall six</code>
<code class="code-inline">$ sudo yum reinstall python-six</code>
<code class="code-inline">$ sudo pip uninstall urllib3</code>
<code class="code-inline">$ sudo yum reinstall python-urllib3</code>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2020-07-29 05:22:11 (台灣)</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-12-26 02:55:25 (台灣)</span></div></pre>
Knuckles
[公司] 營所稅電子申報教學筆記(2019)
http://disp.cc/b/11-bpFf
2019-05-28T19:19:27+08:00
2022-06-13T05:59:54+08:00
這篇只是我自己的筆記備忘,僅供參考,如有錯誤歡迎指正
如有任何問題請洽會計師喔
因為是一人公司,每月只有簡單幾筆收支,營業額不到百萬,
請會計師不太划算,所以才試著自己申報營所稅
可以的話建議還是找個專業會計師來代辦比較好
會計帳及稅額試算
在申報營所稅前要先做好會計帳的「損益表」和「資產負債表」
以下使用這一篇做的會計帳範例來申報
[公司] 用Excel製作簡單的會計帳與財務報表 - KnucklesNote板 - Disp BBS
今年(108)要申報去年(107)的營所稅
參考 自107年度起連續3年營利事業所得稅稅率各不同
去年107年若稅前淨利
12萬元以下 免徵營所稅
12萬~187500元 營所稅 = (稅前淨利 - 12萬)/2
187501~500000元 營所稅 = 稅前淨利 * 18%
50萬元以上 營所稅 = 稅前淨利 * 20%
以及前年(106)年的未分配盈餘稅 = 稅後淨利 * 10%
(107之後改成5%了,參考這篇)
例如會計帳範例去年(107)的稅前淨利不到12萬所以不用繳營所稅,
但是前年(106)的稅後淨利10萬元 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [公司] 營所稅電子申報筆記(2019)<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2019-05-28 Tue. 19:18:23</div><hr color="#008080" />───────────────────
<div class="img" data-ori_w="815" data-ori_h="543" style="width:815px;height:543px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/KmvoiCR.png" alt="[圖]" /></div>
這篇只是我自己的筆記備忘,僅供參考,如有錯誤歡迎指正
如有任何問題請洽會計師喔
因為是一人公司,每月只有簡單幾筆收支,營業額不到百萬,
請會計師不太划算,所以才試著自己申報營所稅
可以的話建議還是找個專業會計師來代辦比較好
<span style="color:#00F000">會計帳及稅額試算</span>
在申報營所稅前要先做好會計帳的「損益表」和「資產負債表」
以下使用這一篇做的會計帳範例來申報
<a href="https://disp.cc/b/11-b4go" target="_blank" rel="nofollow">[公司] 用Excel製作簡單的會計帳與財務報表 - KnucklesNote板 - Disp BBS</a>
<div class="img" data-ori_w="974" data-ori_h="934" style="width:487px;height:467px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/GF2A994.png" alt="[圖]" /></div> <div class="img" data-ori_w="970" data-ori_h="1054" style="width:485px;height:527px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/GsKDobr.png" alt="[圖]" /></div>
今年(108)要申報去年(107)的營所稅
參考 <a href="https://www.ntbk.gov.tw/etwmain/web/ETW118W/CON/934/8085406629279382828" target="_blank" rel="nofollow">自107年度起連續3年營利事業所得稅稅率各不同</a>
去年107年若稅前淨利
12萬元以下 免徵營所稅
12萬~187500元 營所稅 = (稅前淨利 - 12萬)/2
187501~500000元 營所稅 = 稅前淨利 * 18%
50萬元以上 營所稅 = 稅前淨利 * 20%
以及前年(106)年的未分配盈餘稅 = 稅後淨利 * 10%
(107之後改成5%了,<a href="http://www.cheng.company/perspectives/focus/2018-new-rules-4/" target="_blank" rel="nofollow">參考這篇</a>)
例如會計帳範例去年(107)的稅前淨利不到12萬所以不用繳營所稅,
但是前年(106)的稅後淨利10萬元未分配,所以要繳1萬元的未分配盈餘稅
擴大書審申報:
指營業收入及非營業收入合計不超過新台幣3,000萬元之中小企業,
因帳務不健全及為免除國稅局查帳之困擾,縱使帳務結算後之純益率低於擴大書審純益率,
亦同意在結算時同意調整純益率至擴大書審純益率
也就是若損益表算出來的純益率小於擴大書審純益率,就可以考慮使用擴大書審
純益率 = [ 稅前淨利 / ( 營業收入 + 非營業收入 ) ] * 100%
例如會計帳範例的 純益率 = 14140 / (130000 + 200) = 10.86%
在 <a href="http://web02.mof.gov.tw/std/main.htm" target="_blank" rel="nofollow">稅務行業標準分類暨同業利潤標準查詢系統</a>
可依行業代號查詢自己公司的「擴大書審純益率」、「所得額標準」、「同業利潤標準淨利率」
公司的行業代號可在這查詢: <a href="https://www.etax.nat.gov.tw/cbes/web/CBES113W1" target="_blank" rel="nofollow">稅籍登記資料公示查詢</a>
例如 <a href="http://web02.mof.gov.tw/std/wform?noin=vToEUUt3JxubYclm8PkJ&funcid=I-TLM&rev=10&id=6390-99" target="_blank" rel="nofollow">行業代號6390-99的同業利潤標準</a>
<div class="img" data-ori_w="1256" data-ori_h="340" style="width:628px;height:170px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/UAULRjH.png" alt="[圖]" /></div>
擴大書審純益率為 8%
因為在這個範例純益率 10% > 擴大書審純益率 8%,所以不使用擴大書審
<span style="color:#00F000">安裝營所稅申報軟體</span>
到財政部電子申報繳稅服務網 <a href="https://tax.nat.gov.tw/" target="_blank" rel="nofollow">https://tax.nat.gov.tw/</a>
→ 非個人 → 營利事業所得稅 → 營所稅結(決)算申報
<div class="img" data-ori_w="1632" data-ori_h="839" style="width:816px;height:420px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/BtnBAPY.png" alt="[圖]" /></div>
點軟體下載與報稅
<div class="img" data-ori_w="1224" data-ori_h="818" style="width:612px;height:409px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/SaVRx4j.png" alt="[圖]" /></div>
點「結(決)算建檔程式(含審核、列印)」下載安裝
<div class="img" data-ori_w="1600" data-ori_h="1204" style="width:800px;height:602px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/PipG3a4.png" alt="[圖]" /></div>
安裝好後執行
<div class="img" data-ori_w="184" data-ori_h="195" style="width:92px;height:98px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/BlH6QNJ.png" alt="[圖]" /></div>
出現版本檢查按「是」, 確定為最新版後按「進入系統」
<div class="img" data-ori_w="815" data-ori_h="543" style="width:815px;height:543px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/KmvoiCR.png" alt="[圖]" /></div>
<span style="color:#00F000">輸入基本資料</span>
點「資料建檔」
選「結算」(決算是公司要結束營業時用的)
輸入公司統編與名稱,按確定
<div class="img" data-ori_w="411" data-ori_h="267" style="width:411px;height:267px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/ebxXaPr.png" alt="[圖]" /></div>
出現是否要建檔,選確定,進入建檔畫面
<div class="img" data-ori_w="823" data-ori_h="579" style="width:823px;height:579px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/p57aB4s.png" alt="[圖]" /></div>
點「基本資料」
<div class="img" data-ori_w="803" data-ori_h="663" style="width:803px;height:663px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/K4EruaG.png" alt="[圖]" /></div>
公司的行業代號可在這查詢: <a href="https://www.etax.nat.gov.tw/cbes/web/CBES113W1" target="_blank" rel="nofollow">稅籍登記資料公示查詢</a>
若要使用擴大書審,申報種類選「擴大書審」,不使用的話選「非擴大書審」
輸入完後按「存檔」
點「帳簿處理人員及委任代辦申報情形」
<div class="img" data-ori_w="801" data-ori_h="660" style="width:801px;height:660px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/Lq4byjz.png" alt="[圖]" /></div>
選「由本營利事業或關係企業職員擔任會計記帳(免填委任記帳委任書)」
職員或委任人姓名,點後面的「同負責人」
扣繳申報註記,選「未辦理扣繳申報」
輸入完後按「存檔」
點「二、辦理營利事業所得稅結算申報情形」
<div class="img" data-ori_w="799" data-ori_h="660" style="width:799px;height:660px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/FemkuV3.png" alt="[圖]" /></div>
選「由本營利事業自行辦理者(免填以下欄位)」
輸入完後按「存檔」,再按「離開」
<span style="color:#F0F000">第一頁 損益及稅額計算表</span>
在建檔畫面點「結(決)算申報書」
<div class="img" data-ori_w="380" data-ori_h="302" style="width:380px;height:302px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/7WgPFXX.png" alt="[圖]" /></div>
點「第一頁 損益及稅額計算表」
<div class="img" data-ori_w="635" data-ori_h="298" style="width:635px;height:298px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/z0t326P.png" alt="[圖]" /></div>
依會計帳的損益表各項目填入「帳載結算金額」
右邊的「自行依法調整後金額」,如果沒有要調整就和帳載金額相同即可
營業淨利
<div class="img" data-ori_w="764" data-ori_h="697" style="width:764px;height:697px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/pa2WFLX.png" alt="[圖]" /></div>
<div class="img" data-ori_w="764" data-ori_h="697" style="width:764px;height:697px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/ci3dQ3F.png" alt="[圖]" /></div>
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/QhLMD2m.png" alt="[圖]" /></div>
因為營業費用中的「雜項購置」和「手續費」在這邊沒有項目可填
所以加總後填入「其他費用」,之後在第五頁再輸入其他費用明細表
非營業收益
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/HOGWt1V.png" alt="[圖]" /></div>
非營業損失
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/BvoDCw5.png" alt="[圖]" /></div>
損益及課稅所得
<div class="img" data-ori_w="764" data-ori_h="697" style="width:764px;height:697px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/fPZdwRH.png" alt="[圖]" /></div>
如果要使用擴大書審的話,在「自行依法調整後金額」這邊
「全年所得額」要改成 ( 營業收入 + 非營業收入 ) * 擴大書審純益率
這樣「純益率」的「自行依法調整後金額」才會變成自己行業的擴大書審純益率
稅額計算
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/kvmjQQ6.png" alt="[圖]" /></div>
課稅所得額未滿12萬,所以營所稅為0
營業收入調節說明
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/lMR98HM.png" alt="[圖]" /></div>
如果營業收入就是開的發票銷售額的總額,在68填入與01相同的數字即可
<div class="img" data-ori_w="764" data-ori_h="697" style="width:764px;height:697px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/GQJS5ld.png" alt="[圖]" /></div>
營業收入分類表
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/e0AJZUa.png" alt="[圖]" /></div>
若使用擴大書審,輸入行業代碼的擴大書審純益率
不使用擴大書審的話,輸入行業代碼的所得額標準
<div class="long_string">擴大書審純益率或所得額標準,可以在這裡查詢:<a href="http://web02.mof.gov.tw/std/wform?noin=vToEUUt3JxubYclm8PkJ&funcid=I-TLM&rev=10&id=6390-99" target="_blank" rel="nofollow">行業代號6390-99的同業利潤標準</a></div>
輸入完後點「存檔」後再點「離開」
<span style="color:#F0F000">第二頁 所得基本稅額申報表</span>
基本所得額 = 課稅所得額 + 各項免稅所得
107年的 基本稅額 = ( 基本所得額 - 500,000 ) * 12%
若算出來基本稅額比一般所得稅額高,則要再繳多出來的差額
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/8SiEX5G.png" alt="[圖]" /></div>
第一格輸入損益表的課稅所得額
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/lzCADWh.png" alt="[圖]" /></div>
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/smvH6QH.png" alt="[圖]" /></div>
<div class="img" data-ori_w="701" data-ori_h="640" style="width:701px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/AkEc7aZ.png" alt="[圖]" /></div>
如果沒有那些免稅所得的話就不用管這個
按存檔後離開即可
<span style="color:#F0F000">第三頁 資產負債表</span>
依照會計帳的資產負債表輸入即可
資產項目
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/3TACaZz.png" alt="[圖]" /></div>
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/qenyTQy.png" alt="[圖]" /></div>
<div class="img" data-ori_w="599" data-ori_h="661" style="width:599px;height:661px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/vQA1gc8.png" alt="[圖]" /></div>
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/EMQzi1y.png" alt="[圖]" /></div>
<div class="img" data-ori_w="599" data-ori_h="661" style="width:599px;height:661px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/7NqBTkr.png" alt="[圖]" /></div>
負債項目
<div class="img" data-ori_w="599" data-ori_h="661" style="width:599px;height:661px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/8Zjmmwq.png" alt="[圖]" /></div>
<div class="img" data-ori_w="599" data-ori_h="661" style="width:599px;height:661px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/Cew8OvU.png" alt="[圖]" /></div>
負債及業主權益項目
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/QsgaTpB.png" alt="[圖]" /></div>
業主權益項目
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/RHPRf0a.png" alt="[圖]" /></div>
<div class="img" data-ori_w="579" data-ori_h="640" style="width:579px;height:640px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/8rUF99K.png" alt="[圖]" /></div>
輸入完後點「存檔」後再點「離開」
<span style="color:#F0F000">第四頁 營業成本明細表</span>
如果是將薪資列為營業成本的話,將薪資總額填在其他業的「勞務成本」
<div class="img" data-ori_w="661" data-ori_h="525" style="width:661px;height:525px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/5ZudBk0.png" alt="[圖]" /></div>
輸入完後點「存檔」後再點「離開」
<span style="color:#F0F000">第五頁 其他費用明細表</span>
在損益表的營業費用有填「其他費用」的話,在這邊輸入明細表
<div class="img" data-ori_w="681" data-ori_h="424" style="width:681px;height:424px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/1IhszZZ.png" alt="[圖]" /></div>
輸入一筆後點「新增」
輸入完後點「離開」
<span style="color:#F0F000">第八頁 各類給付扣繳稅款與申報金額調節表</span>
各類給付扣繳、股利憑單金額一
<div class="img" data-ori_w="665" data-ori_h="639" style="width:665px;height:639px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/BS49dH3.png" alt="[圖]" /></div>
沒有的話點「存檔」即可
各類給付扣繳、股利憑單金額二
<div class="img" data-ori_w="665" data-ori_h="639" style="width:665px;height:639px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/GRFqovC.png" alt="[圖]" /></div>
沒有的話點「存檔」即可
明細表三
<div class="img" data-ori_w="665" data-ori_h="639" style="width:665px;height:639px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/x64MTHf.png" alt="[圖]" /></div>
沒有話不用管
明細表四 / 關係人負債揭露標準
<div class="img" data-ori_w="665" data-ori_h="639" style="width:665px;height:639px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/fmTchao.png" alt="[圖]" /></div>
勾選「本營利事業無直接或間接對關係人之負債」
明細表四 / 關係人及關係人交易揭露標準
<div class="img" data-ori_w="665" data-ori_h="639" style="width:665px;height:639px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/mP3o9rW.png" alt="[圖]" /></div>
勾選「本營利事業無關係人及關係人之交易」
存檔後離開
<span style="color:#F0F000">第九頁 投資人明細及分配盈餘表</span>
股東無異動,且未分配股利或盈餘的話,第二項打勾即可
<div class="img" data-ori_w="732" data-ori_h="778" style="width:732px;height:778px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/29rfmTl.png" alt="[圖]" /></div>
<span style="color:#F0F000">第十頁 營餘分配表或營餘撥補表</span>
<div class="img" data-ori_w="729" data-ori_h="603" style="width:729px;height:603px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/lhqE3XK.png" alt="[圖]" /></div>
<div class="img" data-ori_w="729" data-ori_h="603" style="width:729px;height:603px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/OO1ndAY.png" alt="[圖]" /></div>
在表10-1(4)的107年12月31日累積營虧餘額的87~106年度,
要輸入資產負債表的「累積營虧」
存檔後離開
<span style="color:#F0F000">第十一頁 未分配盈餘申報書</span>
例如本期是申報去年(107)的營所稅
那前年度(106)的未分配盈餘要在這邊申報
在第一項選「1~2 當年度依商業會計法規定處理之稅後純益」
金額輸入106年的 稅後純益 = 課稅所得額 - 應納稅額
最後算出來的 10% 未分配盈餘稅,在申報上傳時與107年的營所稅一同繳納
<div class="img" data-ori_w="787" data-ori_h="632" style="width:787px;height:632px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/HxQdrJK.png" alt="[圖]" /></div>
存檔後離開
<span style="color:#F0F000">第十三頁 直接劃撥退稅同意書</span>
之前沒填寫過的話在這邊輸入公司戶的銀行帳戶
填寫過的話勾選「本盈利事業已申請直接劃撥退稅者,免填本表。」
<div class="img" data-ori_w="542" data-ori_h="410" style="width:542px;height:410px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/PJUPdGl.png" alt="[圖]" /></div>
存檔後離開
<span style="color:#00F000">匯出申報書與前端審核</span>
關閉申報書資料建檔的視窗,
點資料轉檔/匯出的「申報書資料」
<div class="img" data-ori_w="578" data-ori_h="246" style="width:578px;height:246px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/FJ8slr1.png" alt="[圖]" /></div>
選擇是否要加密,選加密
<div class="img" data-ori_w="395" data-ori_h="202" style="width:395px;height:202px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/nTwj2Np.png" alt="[圖]" /></div>
選擇儲存.001檔的目錄,使用預設的目錄即可
<div class="img" data-ori_w="809" data-ori_h="467" style="width:809px;height:467px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/FWTPZ4M.png" alt="[圖]" /></div>
點「審核申報」
<div class="img" data-ori_w="226" data-ori_h="364" style="width:226px;height:364px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/azAWhr0.png" alt="[圖]" /></div>
點「結算審核與申報作業」
<div class="img" data-ori_w="440" data-ori_h="164" style="width:440px;height:164px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/M16shwS.png" alt="[圖]" /></div>
進入結算申報系統,點「前端審核與申報上傳」
<div class="img" data-ori_w="643" data-ori_h="425" style="width:643px;height:425px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/dVnUif4.png" alt="[圖]" /></div>
選擇之前儲存.001檔的目錄
<div class="img" data-ori_w="787" data-ori_h="434" style="width:787px;height:434px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/WebD0gx.png" alt="[圖]" /></div>
輸入稅籍編號
<div class="img" data-ori_w="284" data-ori_h="149" style="width:284px;height:149px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/AYRhqXd.png" alt="[圖]" /></div>
選擇申報類別
<div class="img" data-ori_w="657" data-ori_h="246" style="width:657px;height:246px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/KhErOSr.png" alt="[圖]" /></div>
非書審的話選「4.公司或其他組織申報營業收入淨額加非營業收入在三千萬以下且非屬擴大書審案件」
書審的話選「7.擴大書審案件」
點「開始審核」
若有錯誤的話會出現
<div class="img" data-ori_w="747" data-ori_h="474" style="width:747px;height:474px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/b26dcv2.png" alt="[圖]" /></div>
依照出現的異常清單,再回去建檔程式一條一條修正
重新轉出.001檔再來審核一次,直到沒有錯誤為止
<span style="color:#00F000">上傳申報書</span>
若沒有錯誤的話會出現
<div class="img" data-ori_w="376" data-ori_h="126" style="width:376px;height:126px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/8fj8ctB.png" alt="[圖]" /></div>
若有股東異動、股份轉讓的話要填寫C4頁公司股東股份轉讓通報表一併申報
沒有的話點確定
<div class="img" data-ori_w="434" data-ori_h="370" style="width:434px;height:370px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/vdLPCqf.png" alt="[圖]" /></div>
點「營所稅結算申報資料上傳」(審核成功才能點)
<div class="img" data-ori_w="657" data-ori_h="246" style="width:657px;height:246px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/ljAKBuE.png" alt="[圖]" /></div>
選擇使用帳號密碼或工商憑証
<div class="img" data-ori_w="280" data-ori_h="188" style="width:280px;height:188px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/DPHrkys.png" alt="[圖]" /></div>
選「線上繳稅」,讀卡機插入金融卡,點「申報上傳」
<div class="img" data-ori_w="519" data-ori_h="255" style="width:519px;height:255px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/ToMURC9.png" alt="[圖]" /></div>
依提示重新插入金融卡與輸入金融卡密碼
繳納成功的明細記錄,點「列印」備存後
<div class="img" data-ori_w="680" data-ori_h="680" style="width:680px;height:680px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/NgJ0MDq.png" alt="[圖]" /></div>
點「確認」離開
申報成功的訊息
<div class="img" data-ori_w="376" data-ori_h="206" style="width:376px;height:206px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/ais8Xn0.png" alt="[圖]" /></div>
<span style="color:#00F000">列印申報書</span>
點「列印申報成功書表」
<div class="img" data-ori_w="614" data-ori_h="182" style="width:614px;height:182px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/3LNoJ7L.png" alt="[圖]" /></div>
勾選公司後,點「列印/儲存」
<div class="img" data-ori_w="745" data-ori_h="595" style="width:745px;height:595px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/unD4Lwz.png" alt="[圖]" /></div>
儲存的PDF檔會像這樣
<div class="img" data-ori_w="1543" data-ori_h="874" style="width:772px;height:437px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/mSvSgFb.png" alt="[圖]" /></div>
右上角蓋收件章處裡面有申報日期時間的才是申報成功的
只要是在申報期間內都可以再重新上傳更正
□ 相關文章
每個單數月15號前要申報營業稅
<a href="https://disp.cc/b/11-SyH" target="_blank" rel="nofollow">公司申報營業稅筆記 - KnucklesNote板 - Disp BBS</a>
每年一月底前要申報各類所得 (主要是薪資支出)
<a href="https://disp.cc/b/11-142X" target="_blank" rel="nofollow">公司申報各類所得資料&開立扣繳憑單 - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2019-05-28 19:18:23</span>
<span class="record">※ 編輯: Knuckles 時間: 2022-06-13 05:59:54 (台灣)</span>
補充; 這是pt 而且是bt站的一種...會紀算你的流量
文章作者可對每則推文做回應...
</div></pre>
Knuckles
[公司] 用Excel製作簡單的會計帳與財務報表
http://disp.cc/b/11-b4go
2019-01-03T02:49:31+08:00
2021-06-19T12:47:30+08:00
這篇只是我自己的筆記備忘,僅供參考,如有錯誤歡迎指正
如有任何問題請洽會計師,不要問我 XD
公司一定要有記帳,帳簿不是用個流水帳記錄花了哪些錢就好
而是有個統一的會計帳記錄方式
申報營所稅時,會需要使用一年的帳簿最後結算的損益表與資產負債表
雖然申報時是不需要附各項花費的憑証資料,但也不能亂寫
因為國稅局會依照公司與各往來廠商的每期申報的營業稅與一月時申報的各類所得資料做勾稽審核
就算營業稅和各類所得沒有異常,只要他覺得你填的金額怪怪的,或只是單純要抽查
就會要求你把帳簿跟各項憑証帶過去給他們檢查
只要被要求查帳,就是麻煩,輕則補稅,重則罰款
而且未來幾年都會被列為查帳戶
因為是一人公司,每月只有簡單幾筆收支,營業額不到百萬,
請會計師不太划算,所以才試著自己做會計帳
可以的話建議還是找個專業會計師來代辦比較好
以下說明範例為服務業,以勞務支出與勞務收入為主
Excel使用捷瑞會計師事務所的會計系統 (免費的Excel檔案)
下載網址: https://www.jrcpa.tw/download.html
下載專區/會計軟體/捷瑞會計系統
下載 Excel 檔後,用 Office 軟 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [公司] 用Excel製作簡單的會計帳與財務報表<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2019-01-03 Thu. 02:49:23</div><hr color="#008080" />───────────────────
<div class="img" data-ori_w="1286" data-ori_h="789" style="width:643px;height:395px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/rRGt8o4.png" alt="[圖]" /></div>
這篇只是我自己的筆記備忘,僅供參考,如有錯誤歡迎指正
如有任何問題請洽會計師,不要問我 XD
公司一定要有記帳,帳簿不是用個流水帳記錄花了哪些錢就好
而是有個統一的會計帳記錄方式
申報營所稅時,會需要使用一年的帳簿最後結算的損益表與資產負債表
雖然申報時是不需要附各項花費的憑証資料,但也不能亂寫
因為國稅局會依照公司與各往來廠商的每期申報的營業稅與一月時申報的各類所得資料做勾稽審核
就算營業稅和各類所得沒有異常,只要他覺得你填的金額怪怪的,或只是單純要抽查
就會要求你把帳簿跟各項憑証帶過去給他們檢查
只要被要求查帳,就是麻煩,輕則補稅,重則罰款
而且未來幾年都會被列為查帳戶
因為是一人公司,每月只有簡單幾筆收支,營業額不到百萬,
請會計師不太划算,所以才試著自己做會計帳
可以的話建議還是找個專業會計師來代辦比較好
以下說明範例為服務業,以勞務支出與勞務收入為主
Excel使用捷瑞會計師事務所的會計系統 (免費的Excel檔案)
下載網址: <a href="https://www.jrcpa.tw/download.html" target="_blank" rel="nofollow">https://www.jrcpa.tw/download.html</a>
下載專區/會計軟體/捷瑞會計系統
下載 Excel 檔後,用 Office 軟體開啟
本文使用 LibreOffice 6.1
開啟時若出現巨集已停用的警告可以不管他,這個檔案沒有用到巨集的功能
<div class="img" data-ori_w="1059" data-ori_h="353" style="width:530px;height:177px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ye2pLu9.png" alt="[圖]" /></div>
先點「說明」頁看一下他提供的使用說明
<div class="img" data-ori_w="1618" data-ori_h="592" style="width:809px;height:296px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/IYb5vaO.png" alt="[圖]" /></div>
先點第一個頁籤「會計科目表」
<div class="img" data-ori_w="892" data-ori_h="506" style="width:446px;height:253px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/IhWJMFP.png" alt="[圖]" /></div>
這邊已經把各種會用到的會計科目都編好編號了
看編號的開頭可以知道這個科目是什麼分類
1:資產 2:負債 3:權益 4:收入 5:成本 6:費用 7:業外收益 8:業外損失 9:所得稅
他有列了五種銀行戶頭,先點頁籤按右鍵解除鎖定,
然後將銀行戶頭改成自己有使用的,再將頁籤設為鎖定
點第二頁「日記簿」,可以在這邊開始記帳
<div class="img" data-ori_w="1495" data-ori_h="529" style="width:748px;height:265px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/0UZmG6c.png" alt="[圖]" /></div>
將○○有限公司改為自己的公司名稱,2018年度改為要記帳的年度
第三頁「列印傳票」
<div class="img" data-ori_w="1387" data-ori_h="781" style="width:694px;height:391px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/5h9zuoz.png" alt="[圖]" /></div>
將○○有限公司改為自己的公司名稱
可以在右邊選取在日記簿新增的分錄,就會將該分錄顯示在左邊的傳票上
將傳票印出來後,再把相關的憑証(發票、收據等)貼在上面,保存在帳冊裡
第四頁「資產負債表」,會自動依日記簿產生年底的資產負債表
<div class="img" data-ori_w="966" data-ori_h="658" style="width:483px;height:329px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/WQNjihC.png" alt="[圖]" /></div>
將○○有限公司改為自己的公司名稱,截止日輸入記帳年度的12/31
第五頁「損益表」,會自動依日記簿產生年度的損益表
<div class="img" data-ori_w="1089" data-ori_h="886" style="width:545px;height:443px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/s9K2bjg.png" alt="[圖]" /></div>
將○○有限公司改為自己的公司名稱,起訖期間輸入記帳年度的1/1~12/31
第六頁「分類帳」
<div class="img" data-ori_w="1364" data-ori_h="339" style="width:682px;height:170px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/eO6zUtV.png" alt="[圖]" /></div>
將○○有限公司改為自己的公司名稱,起訖期間輸入記帳年度的1/1~12/31
輸入會計科目的編號,會將該科目在日記簿的所有借貸金額以及餘額列出來
回到「日記簿」,開始輸入各種收支的分錄
以下例子可以直接下載 <a href="https://knuckles.disp.cc/temp/107AccountsExample.xls" target="_blank" rel="nofollow">輸入好的範例檔</a> 來看
1. 開帳分錄
這邊使用一個年度開一個新的檔案的方式
所以第一筆分錄要記錄去年12/31時的結帳金額
也就是去年的資產負債表,例如去年公司才成立,資本額20萬
最後結算賺了10萬,資產負債表會像這樣:
資產 負債與股東權益
現金 100,000 應付費用 2,000
存款 200,000
應收帳款 1,000 實收資本 200,000
留抵稅額 1,000 本期損益 100,000
填入開帳分錄後會像這樣:
<div class="img" data-ori_w="1490" data-ori_h="496" style="width:745px;height:248px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/0MqVube.png" alt="[圖]" /></div>
傳票編號是自訂的,不要重覆即可,例如使用年份+月份+流水號
傳票日期填入去年的12/31
科目代號可以查前一頁的會計科目表,輸入代號後會自動產生科目名稱
然後將資產類的金額都寫在借,負債與股東權益的金額都寫在貸
其中今年的「累積盈虧」= 去年的「累積盈虧」+ 去年的「本期損益」
1105 現金 借 100,000
1112 銀行存款 借 200,000
1133 應收帳款 借 1,000
1295 留抵稅額 借 1,000
2145 應付費用 貸 2,000
3110 實收資本 貸 200,000
3500 累積盈虧 貸 100,000
講一下會計方程式,大概知道一下就好了
每一筆交易,一定要記錄少了哪些東西,然後多了哪些東西
而那些東西的名字,有一個表,叫做會計科目表
裡面將各種交易項目分成了五大類:資產、負債、業主權益、收入、費用
所有交易會符合一個會計方程式:
資產 = 負債 + 業主權益 ,其中 業主權益 = 資本 + 收入 - 費用
= 負債 + 資本 + 收入 - 費用
借貸法則: 使等號左邊 增加的叫借 減少的叫貸
使等號右邊 增加的叫貸 減少的叫借
就記得左借右貸,每筆交易,都會有個借跟貸
所以每筆交易可能為左邊資產增加(借),左邊資產減少(貸)
或是左邊資產和右邊負債同時增加或減少
最後把所有項目加起來,就是今年的資產負債表啦
把所有的收入與費用列出來,就是損益表了
2. 支付應付費用
把去年欠的應付費用 2000 用現金付清,新增一筆傳票
2145 應付費用 借 2000 (負債減少)
1105 現金 貸 2000 (資產減少)
像這樣,與前一個傳票間空一行後再輸入新的傳票
<div class="img" data-ori_w="1495" data-ori_h="413" style="width:748px;height:207px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/lbfvDhp.png" alt="[圖]" /></div>
要注意因為每列的前面有隱藏欄位,所以要用複製貼上的話
一定都要用整列複製再整列貼上
<div class="img" data-ori_w="1511" data-ori_h="194" style="width:756px;height:97px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/bYTItDW.png" alt="[圖]" /></div>
如果使用插入新的列,會出現公式遺失的訊息
<div class="img" data-ori_w="479" data-ori_h="63" style="width:240px;height:32px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/D7t9D3H.png" alt="[圖]" /></div>
只要複製其他列再貼上新插入的列即可
如果要複製多列插入某個位置的話
例如選取這三列,然後點複製
<div class="img" data-ori_w="1505" data-ori_h="367" style="width:753px;height:184px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/5kIMj2Z.png" alt="[圖]" /></div>
在要插入的列,點「選擇性貼上」
<div class="img" data-ori_w="1497" data-ori_h="258" style="width:749px;height:129px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/SZ3mGQV.png" alt="[圖]" /></div>
選「全部貼上」,移動儲存格選「向下」
<div class="img" data-ori_w="350" data-ori_h="580" style="width:175px;height:290px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/7NuQL0u.png" alt="[圖]" /></div>
就可以插入複製的多列資料了
<div class="img" data-ori_w="1524" data-ori_h="407" style="width:762px;height:204px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/y9KfaAP.png" alt="[圖]" /></div>
3. 支付薪資
使用現金支付薪資4萬給員工,因為是服務業主要以勞務來賺錢,
是將薪資列為成本,而不是費用,
所以會計科目使用勞務成本,而不是費用類的薪資支出
5200 勞務成本 借 40,000 (費用增加)
1105 現金 貸 40,000 (資產減少)
4. 補充現金
若現金不敷使用了,要從帳戶轉5萬出來,轉帳時有15塊手續費
1105 現金 借 50,000 (資產增加)
6270 手續費 借 15 (費用增加)
1112 銀行存款 貸 50,015 (資產減少)
5. 支付其他公司費用
有購買A公司的廣告服務,A公司開了發票來請款,銷售額 10,000,稅額 500
使用銀行轉帳 10,500,被扣了 15 元手續費,總共付了 10,515
6080 廣告費 借 10,000 (費用增加)
1290 進項稅額 借 500 (資產增加)
6270 手續費 借 15 (費用增加)
1112 銀行存款 貸 10,515 (資產減少)
若講好手續費由對方公司負擔的話,就只要轉 10,485,再扣15元變成 10,500
分錄就不用加上手續費
6080 廣告費 借 10,000
1290 進項稅額 借 500
1112 銀行存款 貸 10,500
若收到發票沒有要立刻付款,而是要過幾個月再付的話,可以先列為應付費用
6080 廣告費 借 10,000 (費用增加)
1290 進項稅額 借 500 (資產增加)
2145 應付費用 貸 10,500 (負債增加)
等到要支付的時候開一筆分錄
2145 應付費用 借 10,500 (負債減少)
6270 手續費 借 15 (費用增加)
1112 銀行存款 貸 10,515 (資產減少)
6. 收到應收帳款
收到B公司去年的應收帳款 1000 元,使用轉帳到公司帳戶
1112 銀行存款 借 1,000 (資產增加)
1133 應收帳款 貸 1,000 (資產減少)
7. 勞務收入,開發票請款
例如完成了C公司一件工作,賺了1萬元
開給了C公司一張發票請款,銷售額 10,000,稅額 500
1133 應收帳款 借 10,500 (資產增加)
4200 勞務收入 貸 10,000 (收入增加)
2290 銷項稅額 貸 500 (負債增加)
等收到錢的時候,再開一筆分錄
1112 銀行存款 借 10,500 (資產增加)
1133 應收帳款 貸 10,500 (資產減少)
若開了發票對方很快就付款,也可以直接合成一筆分錄就好
1112 銀行存款 借 10,500 (資產增加)
4200 勞務收入 貸 10,000 (收入增加)
2290 銷項稅額 貸 500 (負債增加)
8. 收到被扣手續費的應收帳款
若C公司要我們來負擔手續費的話,就只會轉來 10,485 元
那分錄就要加上手續費
1112 銀行存款 借 10,485 (資產增加)
6270 手續費 借 15 (費用增加)
1133 應收帳款 貸 10,500 (資產減少)
9. 雜項購置
例如用現金購買一些辦公室用具的費用,銷售額 10,000,稅額 500
列入費用的雜項購置
6250 雜項購置 借 10,500 (費用增加)
1290 進項稅額 借 500 (資產增加)
1105 現金 貸 10,500 (資產減少)
10. 月底分錄,銷項稅額 < 進項稅額 + 留抵稅額
每個雙數月底要結算累積的銷項稅額與進項稅額,
兩個相抵轉為留抵稅額或是應納稅額,
例如一二月合計銷項稅額 500,進項稅額 1000,去年有累積的留抵稅額 1000,
相抵之後轉為留抵稅額為 1500,則在二月底新增一筆月底分錄為
2290 銷項稅額 借 500 (負債減少)
1295 留抵稅額 借 1,500 (資產增加)
1290 進項稅額 貸 1,000 (資產減少)
1295 留抵稅額 貸 1,000 (資產減少)
11. 月底分錄,銷項稅額 > 進項稅額 + 留抵稅額
若是銷項稅額比較多的話,
例如三四月銷項稅額 2000,進項稅額 100,上期留抵稅額 1500
相抵之後轉為應納稅額 400,則在四月底新增一筆月底分錄為
2290 銷項稅額 借 2,000 (負債減少)
1290 進項稅額 貸 100 (資產減少)
1295 留抵稅額 貸 1,500 (資產減少)
2295 應納稅額 貸 400 (負債增加)
12. 繳納營業稅
在單數月的15號前,如果上一期月底分錄有應納稅額的話,要繳營業稅
例如在 5/15 繳納三四月的營業稅 400 元
2295 應納稅額 借 400 (負債減少)
1105 現金 貸 400 (資產減少)
13. 銀行利息
銀行帳戶產生的利息屬於營業外收益的利息收入
1112 銀行存款 借 100 (資產增加)
7040 利息收入 貸 100 (收入增加)
14. 營所稅
到了年底要開一個年底分錄,記錄今年的營業所得稅要繳多少
先在損益表那一頁找到自動算出來的 稅前淨利 = 營業收入 + 非營業收入 - 營業成本 - 營業費用
107年若稅前淨利 12萬元以下 免徵營所稅
12萬~187500元 營所稅 = (稅前淨利 - 12萬)/2
187501~500000元 營所稅 = 稅前淨利 * 18%
50元以上 營所稅 = 稅前淨利 * 20%
若去年的稅後淨利在今年申報營所稅時選擇不分配給股東,
那就還要繳一筆 未分配盈餘稅 = 稅後淨利 * 10%
(之後改成5%了,<a href="http://www.cheng.company/perspectives/focus/2018-new-rules-4/" target="_blank" rel="nofollow">參考這篇</a>)
例如今年(107)的稅前淨利不到12萬所以不用繳營所稅,
但是去年(106)的稅後淨利10萬元未分配,要繳1萬元的未分配盈餘稅
所以新增一筆12/31的年底分錄
9000 所得稅費用 借 10,000 (費用增加)
2295 應納稅額 貸 10,000 (負債增加)
然後在明年(108)的五月底前繳納時再新增一筆分錄
2295 應納稅額 借 10,000 (負債減少)
1105 現金 貸 10,000 (資產減少)
將一整年的日記簿都輸入完之後,就有自動產生的資產負債表和損益表了
產生的資產負債表像這樣,可以使用左上角的篩選鈕,隱藏沒有值的項目
<div class="img" data-ori_w="970" data-ori_h="1054" style="width:485px;height:527px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/GsKDobr.png" alt="[圖]" /></div>
明年的開帳分錄的項目就是用這個資產負債表來填寫
資產負債表就是將日記簿中每個會計科目一整年的金額加起來
將借貸沒有互相抵相的項目列出來
其中本期損益 4140 的值是從損益表來的
在日記簿中的收入、成本、費用這些科目會列在損益表合成一筆「本期損益」
再放進資產負債表中
損益表會像這樣
<div class="img" data-ori_w="974" data-ori_h="934" style="width:487px;height:467px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/GF2A994.png" alt="[圖]" /></div>
可以看到雖然稅前淨利14140不到12萬不用繳營所稅,
但是要繳去年的未分配盈餘稅1萬,所以稅後淨利為 4140
其中所得稅費用是個特殊的會計科目,自己單獨為一個類別
在損益表中不會跟其他的費用算在一起,而是放在稅前淨利後用來算出稅後淨利
有了資產負債表和損益表,就可以在明年五月申報營所稅時,照這個輸入了
另外還有分類帳的功能可以用來檢查每個會計科目的收支情況
例如輸入銀行存款的代號 1112,就可以看到存款的收支記錄
<div class="img" data-ori_w="1362" data-ori_h="623" style="width:681px;height:312px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/pmcfDSz.png" alt="[圖]" /></div>
要注意日記簿的收支記錄可以不用完全跟銀行存款實際的收支記錄一樣,
但是到年底的存款餘額要一樣,如果不一樣的話就要調整一下
□ 相關文章
每個單數月15號前要申報營業稅
<a href="https://disp.cc/b/11-SyH" target="_blank" rel="nofollow">公司申報營業稅筆記 - KnucklesNote板 - Disp BBS</a>
每年一月底前要申報各類所得 (主要是薪資支出)
<a href="https://disp.cc/b/11-142X" target="_blank" rel="nofollow">公司申報各類所得資料&開立扣繳憑單 - KnucklesNote板 - Disp BBS</a>
每年五月底前要申報營業所得稅
<a href="https://disp.cc/b/11-bpFf" target="_blank" rel="nofollow">[公司] 營所稅電子申報筆記(2019) - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2019-01-03 02:49:23</span>
<span class="record">※ 編輯: Knuckles 時間: 2021-06-19 12:47:30 (台灣)</span>
補充; 這是pt 而且是bt站的一種...會紀算你的流量
文章作者可對每則推文做回應...
</div></pre>
Knuckles
[MySQL] 設定Master/Slave資料庫備援(Replication)
http://disp.cc/b/11-b2UR
2018-12-25T02:28:44+08:00
2023-11-19T19:05:50+08:00
本文使用的作業系統: CentOS 7,
資料庫使用 MariaDB 5.5 (與 MySQL 5.5 相容)
資料庫可以使用 Master/Slave 架構,
讓主資料庫 Master 主要負責資料寫入的動作,
然後自動將所有更新同步到備援資料庫 Slave
讓備援資料庫 Slave 用來分擔大部份的資料讀取動作
另外也可以當作資料庫的即時備份
主資料庫 Master 設定
修改設定檔 my.cnf
$ sudo vim /etc/my.cnf
# 不同的資料庫要用不用的 id 數字,通常主資料庫就設定為 1
server-id = 1
# 將資料庫的每個寫入指令記錄在 binlog 檔,用來讓備援資料庫同步用
log-bin=mysql-bin
# 設定 binlog 一天就到期自動刪除,免得占用過多硬碟空間
expire-logs-days=1
# 要忽略某些資料庫不要記錄的話可以設定
binlog_ignore_db=test1
binlog_ignore_db=test2
# 每次的寫入指令都會記錄在 binlog,確保資料即時被同步到備援資料庫,會影響效能
sync_bin ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [MySQL] 設定Master/Slave資料庫備援(Replication)<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2018-12-25 Tue. 02:27:37</div><hr color="#008080" />───────────────────
本文使用的作業系統: CentOS 7,
資料庫使用 MariaDB 5.5 (與 MySQL 5.5 相容)
資料庫可以使用 Master/Slave 架構,
讓主資料庫 Master 主要負責資料寫入的動作,
然後自動將所有更新同步到備援資料庫 Slave
讓備援資料庫 Slave 用來分擔大部份的資料讀取動作
另外也可以當作資料庫的即時備份
<span style="color:#00F000">主資料庫 Master 設定</span>
修改設定檔 my.cnf
<code class="code-inline">$ sudo vim /etc/my.cnf</code>
# 不同的資料庫要用不用的 id 數字,通常主資料庫就設定為 1
server-id = 1
# 將資料庫的每個寫入指令記錄在 binlog 檔,用來讓備援資料庫同步用
log-bin=mysql-bin
# 設定 binlog 一天就到期自動刪除,免得占用過多硬碟空間
expire-logs-days=1
# 要忽略某些資料庫不要記錄的話可以設定
binlog_ignore_db=test1
binlog_ignore_db=test2
# 每次的寫入指令都會記錄在 binlog,確保資料即時被同步到備援資料庫,會影響效能
sync_binlog = 1
# 有使用 innodb 的話,要設定這個確保資料會寫入硬碟,會影響效能
innodb_flush_log_at_trx_commit = 1
重啟讓設定生效
<code class="code-inline">$ sudo systemctl restart mariadb</code>
在主資料庫新增一個使用者 replication
用來給備援資料庫登入用
先登入mysql指令模式
<code class="code-inline">$ mysql -u root -p</code>
新增使用者,將 <span style="color:#008080">rep_password</span> 改為自訂的密碼
<code class="code-inline">MariaDB > CREATE USER 'replication'@'%' IDENTIFIED BY '<span style="color:#008080">rep_password</span>';</code>
<code class="code-inline">MariaDB > GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';</code>
<code class="code-inline">MariaDB > FLUSH PRIVILEGES;</code>
<span style="color:#00F000">備援資料庫 Slave 設定</span>
修改設定檔 my.cnf
<code class="code-inline">$ sudo vim /etc/my.cnf</code>
# 備援資料庫的 id 設為 2
server-id = 2
# 設為唯讀避免非管理者帳號寫入資料
read_only
# 設定 Slave 的IP,讓 Master 可以使用 show slave hosts 顯示有在連線的 Slave
report-host=db2
# 如果要忽略某些資料庫,不同步過來的話可以設定這個
# 多個資料庫的話要分行設定,不能用逗號分隔
replicate_ignore_db=test1
replicate_ignore_db=test2
# 如果要忽略某些資料表,可以設定這個
# 多個talbe要分行設定,不能用逗號分隔
replicate_ignore_table=test1.table1
replicate_ignore_table=test1.table2
重啟讓設定生效
<code class="code-inline">$ sudo systemctl restart mariadb</code>
<span style="color:#00F000">在 Master 使用 mysqldump 備份整個資料庫</span>
要先將主資料庫完整的複製到備援資料庫後,才能開始同步
在使用 mysqldump 前,要停止資料庫所有的寫入動作,避免之後同步失敗
先將網頁讀取資料庫的帳號設為唯讀 (只有 SELECT 權限)
然後開一個新的ssh連線登入 mysql 指令模式執行
<code class="code-inline">MariaDB > FLUSH TABLES WITH READ LOCK;</code>
將還在記憶體的資料寫進binlog,並鎖定所有資料表
取得目前 binlog 的記錄位置
<code class="code-inline">MariaDB > SHOW MASTER STATUS\G;</code>
<div class="highlight"><span></span>*************************** 1. row ***************************
File: mysql-bin.000059
Position: 3847499
Binlog_Do_DB:
Binlog_Ignore_DB:
</div>
記下 File 和 Position,之後在 Slave 要設定從這邊開始同步
注意只要離開 mysql 指令模式後,鎖定就會解開了
所以要用另一個連線來執行 mysqldump
在另一個ssh連線執行 mysqldump 備份整個資料庫
<code class="code-inline">$ mysqldump -u root -p --all-databases --single-transaction --flush-logs > dump_master.sql</code>
或是只備份指定的資料庫 my_db
<code class="code-inline">$ mysqldump -u root -p mh_db --single-transaction --flush-logs > dump_master.sql</code>
備份完成後就可以解除鎖定了,回到 mysql 指令模式的連線執行
MariaDB > UNLOCK TABLES;
或是用 exit; 離開也可以
將網頁讀取資料庫的帳號解除唯讀
使用 scp 將 dump_master.sql 傳給 Slave 主機
$ sudo scp dump_master.sql root@db2:/root/
以上步驟也可以寫成 Shell script
<div class="highlight"><span></span><span class="ch">#!/bin/bash</span>
<span class="nv">myUser</span><span class="o">=</span><span class="s2">"your_username"</span>
<span class="nv">myPass</span><span class="o">=</span><span class="s2">"your_password"</span>
<span class="nv">myDB</span><span class="o">=</span><span class="s2">"your_db"</span>
<span class="nv">backup_dir</span><span class="o">=</span><span class="s2">"/root/"</span>
<span class="nv">dump_file</span><span class="o">=</span><span class="nv">$backup_dir</span><span class="s2">"dump_master.sql"</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">"使用 mysqldump 備份資料庫 存成 dump_master.sql 檔"</span>
mysqldump<span class="w"> </span>-u<span class="w"> </span><span class="nv">$myUser</span><span class="w"> </span>-p<span class="nv">$myPass</span><span class="w"> </span><span class="nv">$myDB</span><span class="w"> </span>--single-transaction<span class="w"> </span>--flush-logs<span class="w"> </span>><span class="w"> </span><span class="nv">$dump_file</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">"使用 scp 將 dump_master.sql 傳送給 Slave 主機"</span>
scp<span class="w"> </span><span class="nv">$dump_file</span><span class="w"> </span>root@db2:<span class="nv">$backup_dir</span>
<span class="c1"># 執行 SHOW MASTER STATUS 並取出 File 與 Position 的值</span>
<span class="nv">QUERY</span><span class="o">=</span><span class="s2">"SHOW MASTER STATUS\G;"</span>
<span class="nv">MYSQL_RESULT</span><span class="o">=</span><span class="k">$(</span>mysql<span class="w"> </span>-u<span class="nv">$myUser</span><span class="w"> </span>-p<span class="nv">$myPass</span><span class="w"> </span>-e<span class="w"> </span><span class="s2">"</span><span class="nv">$QUERY</span><span class="s2">"</span><span class="k">)</span>
<span class="nv">FILE</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">"</span><span class="nv">$MYSQL_RESULT</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">'/File/ {print $2}'</span><span class="k">)</span>
<span class="nv">POSITION</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">"</span><span class="nv">$MYSQL_RESULT</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">'/Position/ {print $2}'</span><span class="k">)</span>
<span class="c1"># 產生 SQL 指令檔傳給 SLAVE 主機</span>
<span class="nv">SQL_FILE</span><span class="o">=</span><span class="s2">"change_master.sql"</span>
<span class="nv">REP_PASS</span><span class="o">=</span><span class="s2">"<span style="color:#008080">rep_password</span>"</span>
<span class="nv">SQL</span><span class="o">=</span><span class="s2">"STOP SLAVE; CHANGE MASTER TO MASTER_HOST='db3',</span>
<span class="s2"> MASTER_USER='replication', MASTER_PASSWORD='"</span><span class="nv">$REP_PASS</span><span class="s2">"',</span>
<span class="s2"> MASTER_LOG_FILE='</span><span class="nv">$FILE</span><span class="s2">', MASTER_LOG_POS=</span><span class="nv">$POSITION</span><span class="s2">; START SLAVE;"</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$SQL</span><span class="w"> </span>><span class="w"> </span><span class="nv">$SQL_FILE</span>
scp<span class="w"> </span><span class="nv">$SQL_FILE</span><span class="w"> </span>root@db2:<span class="nv">$backup_dir</span>
</div>
其中 awk '/File/ {print $2}' 代表取出含有 File 那行的第二欄
<span style="color:#00F000">在 Slave 啟動同步功能</span>
將 Master 傳來的資料庫備份檔 dump_master.sql 還原進資料庫
<code class="code-inline">$ mysql -u root -p < dump_master.sql</code>
在 Slave 主機登入 mysql 指令模式,執行
<div class="highlight"><span></span><span class="n">MariaDB</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">STOP</span><span class="w"> </span><span class="n">SLAVE</span><span class="p">;</span>
<span class="n">CHANGE</span><span class="w"> </span><span class="n">MASTER</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="n">MASTER_HOST</span><span class="o">=</span><span class="s1">'db1'</span><span class="p">,</span>
<span class="w"> </span><span class="n">MASTER_USER</span><span class="o">=</span><span class="s1">'replication'</span><span class="p">,</span>
<span class="w"> </span><span class="n">MASTER_PASSWORD</span><span class="o">=</span><span class="s1">'rep_password'</span><span class="p">,</span>
<span class="w"> </span><span class="n">MASTER_LOG_FILE</span><span class="o">=</span><span class="s1">'mysql-bin.000059'</span><span class="p">,</span>
<span class="w"> </span><span class="n">MASTER_LOG_POS</span><span class="o">=</span><span class="mi">3847499</span><span class="p">;</span>
<span class="k">START</span><span class="w"> </span><span class="n">SLAVE</span><span class="p">;</span>
</div>
將上面的 db1 改為主資料庫的 hostname 或是 IP 位址
rep_password 改為之前新增的使用者 replication 的密碼
mysql-bin.000059 和 3847499 改為前面記下的 File 和 Position
或是使用 Master 傳來的 SQL 指令檔
<code class="code-inline">$ mysql -u root -p < change_master.sql</code>
看看 Slave 主機有沒有正確在執行同步
<code class="code-inline">MariaDB > SHOW SLAVE STATUS\G;</code>
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: db1
Master_User: replication
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000060
Read_Master_Log_Pos: 16172358
Relay_Log_File: db2-relay-bin.000004
Relay_Log_Pos: 16172642
Relay_Master_Log_File: mysql-bin.000060
Slave_IO_Running: <span style="color:#00F000">Yes</span>
Slave_SQL_Running: <span style="color:#00F000">Yes</span>
.....
若上面的 Slave_IO_Running 和 Slave_SQL_Running 都是 Yes 的話就是成功了
有使用 phpMyAdmin 的話可以在 Master 主機的「伺服器/備援」看到
<div class="img" data-ori_w="1171" data-ori_h="917" style="width:586px;height:459px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/MQouqLt.png" alt="[圖]" /></div>
前面兩個指令就是前面提過的
<code class="code-inline">MariaDB > SHOW MASTER STATUS;</code>
<code class="code-inline">MariaDB > SHOW SLAVE HOSTS;</code>
在 Slave 主機的「伺服器/備援」看到
<div class="img" data-ori_w="985" data-ori_h="615" style="width:493px;height:308px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/mNBuoxA.png" alt="[圖]" /></div>
其中查看次要伺服器狀態就是前面提過的
<code class="code-inline">MariaDB > SHOW SLAVE STATUS\G;</code>
<div class="img" data-ori_w="767" data-ori_h="974" style="width:384px;height:487px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/A1uENA6.png" alt="[圖]" /></div>
可以過段時間看一下Running是不是 Yes,以及 Errno 是否為0
Slave主機可以關機一下再打開,一樣可以同步到最新的資料
若確認資料庫同步都不會出現錯誤後,就可以改寫 PHP 存取資料庫的程式,
將 SELECT 的 SQL 改為使用備援資料庫
例如是使用 mysqli 來存取資料庫時,原本取得連線的程式為
<div class="highlight"><span></span><span class="nv">$mysqli</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">mysqli</span><span class="p">(</span><span class="s2">"db1"</span><span class="p">,</span> <span class="nv">$user</span><span class="p">,</span> <span class="nv">$pswd</span><span class="p">,</span> <span class="nv">$db_name</span><span class="p">);</span>
</div>
再另外新增一個
<div class="highlight"><span></span><span class="nv">$mysqli_slave</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">mysqli</span><span class="p">(</span><span class="s2">"db2"</span><span class="p">,</span> <span class="nv">$user</span><span class="p">,</span> <span class="nv">$pswd</span><span class="p">,</span> <span class="nv">$db_name</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$mysqli_slave</span><span class="o">-></span><span class="na">connect_errno</span><span class="p">){</span> <span class="c1">//備援DB連不上時,使用主DB</span>
<span class="tab"> </span><span class="nb">unset</span><span class="p">(</span><span class="nv">$mysqli_slave</span><span class="p">);</span>
<span class="tab"> </span><span class="nv">$mysqli_slave</span> <span class="o">=</span> <span class="nv">$mysqli</span><span class="p">;</span>
<span class="p">}</span>
</div>
然後將使用 SELECT 的 SQL 改為使用 $mysqli_slave 這個連線即可
另外要注意讀取型態為 MEMORY 的資料表不要使用 Slave
因為 Slave 主機只要重開機後資料表被清空,就會與主資料庫的不相同了
<span style="color:#00F000">自動檢查 Slave 主機狀態</span>
寫一個 shell 檔 check_slave.sh 每10分鐘自動檢查 Slave 主機是否有在同步,
若沒有的話產生一個 slave_error 的檔案,讓 PHP 可以知道 Slave 掛了不要使用
<div class="highlight"><span></span><span class="ch">#!/bin/bash</span>
<span class="nv">myUser</span><span class="o">=</span><span class="s2">"your_username"</span>
<span class="nv">myPswd</span><span class="o">=</span><span class="s2">"your_password"</span>
<span class="nv">myStatus</span><span class="o">=(</span><span class="sb">`</span>mysql<span class="w"> </span>-u<span class="nv">$myUser</span><span class="w"> </span>-p<span class="nv">$myPswd</span><span class="w"> </span>-e<span class="w"> </span><span class="s2">"SHOW SLAVE STATUS\G;"</span><span class="w"> </span><span class="p">|</span>egrep<span class="w"> </span><span class="s2">"Slave_IO_Running|Slave_SQL_Running"</span><span class="w"> </span><span class="p">|</span>awk<span class="w"> </span><span class="s1">'{print $NF}'</span><span class="sb">`</span><span class="o">)</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">myStatus</span><span class="p">[0]</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"No"</span><span class="w"> </span>-o<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">myStatus</span><span class="p">[1]</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"No"</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span>touch<span class="w"> </span>slave_error
<span class="w"> </span>scp<span class="w"> </span>slave_error<span class="w"> </span>your_admin_id@your_web_host:/var/www/your_path/
<span class="k">fi</span>
</div>
其中使用 |egrep 將 SLAVE STAUS 中的 Slave_IO_Running 與 Slave_SQL_Running 這兩行抓出來
使用 |awk '{print $NF}' 可以將那兩行的最後一個字串抓出來
若 Slave 主機正常的話,抓出來的結果為兩行都是「Yes」,有錯的話其中一行會是「No」
存到 myStatus 後可使用 ${myStatus[0]} 與 ${myStatus[1]} 來判斷是否有「No」
有「No」的話用 touch 產生一個 slave_error 檔
用 scp 將 slave_error 檔傳到 web 主機的網頁目錄
在 /etc/crontab 每十分鐘執行一次檢查
<div class="highlight"><span></span>*/10 * * * * your_admin_id /home/your_admin_id/check_slave.sh
</div>
在 PHP 連線資料庫時多加個判斷
<div class="highlight"><span></span><span class="nv">$slave_error</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="nx">slave_error</span><span class="p">)</span> <span class="o">&&</span> <span class="nb">time</span><span class="p">()</span><span class="o">-</span><span class="nb">filemtime</span><span class="p">(</span><span class="nx">slave_error</span><span class="p">)</span><span class="o"><</span><span class="mi">600</span><span class="p">){</span>
<span class="tab"> </span><span class="nv">$slave_error</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
</div>
參考:
<a href="https://blog.toright.com/posts/5062/mysql-replication-%E4%B8%BB%E5%BE%9E%E5%BC%8F%E6%9E%B6%E6%A7%8B%E8%A8%AD%E5%AE%9A%E6%95%99%E5%AD%B8.html" target="_blank" rel="nofollow">MySQL Replication 主從式架構設定教學</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2018-12-25 02:27:37</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-19 19:05:50 (台灣)</span></div></pre>
Knuckles
[Xcode][Swift4] 更新為 Xcode9 後出現的警告
http://disp.cc/b/11-aMHp
2018-08-18T00:21:45+08:00
2018-08-18T02:29:03+08:00
● 'characters' is deprecated
在 Swift 3 要取得字串長度時,是用
myString.characters.count
但更新為 Xcode 9 後支援 Swift 4,執行後會出現警告:
Swift Compiler Warning
'characters' is deprecated: Please use String or Substring directly
Swift 4 的字串可以直接用 .count 取得字串長度了,所以只要把 .characters 去掉即可:
myString.count
同樣的其他在 characters 下的成員,像是 .first .index
現在都可以直接在字串下取用了
在函式庫 Pods/Alamofire/Request.swift 裡也有用到 characters
要修改前 Xcode 會說這個檔案是 lock 的,要先 unlock 嗎,點 unlock 即可
(不要在 Finder 中再改回鎖定,不然 pod update 會無法存取)
● Vali ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift4] 更新為 Xcode 9 後出現的警告<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2018-08-18 Sat. 00:22:08</div><hr color="#008080" />───────────────────
● 'characters' is deprecated
在 Swift 3 要取得字串長度時,是用
myString.characters.count
但更新為 Xcode 9 後支援 Swift 4,執行後會出現警告:
Swift Compiler Warning
'characters' is deprecated: Please use String or Substring directly
Swift 4 的字串可以直接用 .count 取得字串長度了,所以只要把 .characters 去掉即可:
myString.count
同樣的其他在 characters 下的成員,像是 .first .index
現在都可以直接在字串下取用了
在函式庫 Pods/Alamofire/Request.swift 裡也有用到 characters
要修改前 Xcode 會說這個檔案是 lock 的,要先 unlock 嗎,點 unlock 即可
(不要在 Finder 中再改回鎖定,不然 pod update 會無法存取)
● Validate Project Setting
點一下主 project 的 Update to recommended settings
<div class="img" data-ori_w="1475" data-ori_h="569" style="width:738px;height:285px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/0bVQU2r.png" alt="[圖]" /></div>
在點「Perform Changes」前最好先 commit 一下專案
點了「Perform Changes」後再執行看看有沒有多警告出來
沒有的話對 Pods Project 也執行 Update to recommended settings
● Swift Conversion
要自動將程式轉成 Swift 4 之前先 Commit
點一下 Conversion to Swift 4 is available
<div class="img" data-ori_w="1590" data-ori_h="682" style="width:795px;height:341px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/QzFQ6PM.png" alt="[圖]" /></div>
Swift 4 @objc Inference: 選 Minimize Inference (recommended)
<div class="img" data-ori_w="1131" data-ori_h="657" style="width:566px;height:329px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Nt12BUk.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2018-08-18 00:22:08</span>
<span class="record">※ 編輯: Knuckles 時間: 2018-08-18 02:29:27</span></div></pre>
Knuckles
[Xcode][Swift4] 更新為 Xcode9 後出現的 Auto Layout 警告
http://disp.cc/b/11-aMvM
2018-08-16T01:28:55+08:00
2018-08-18T01:55:35+08:00
將 Xcode 從 8 更新到 9 後,在 Storyboard 可能會出現一堆警告
因為 Xcode 9 的 Auto Layout 變嚴格了
因為擔心文字如果使用不同語言可能會被裁切,或是跟其他元件重疊
如果確定不會用其他語言也可以不理會警告,或是找方法調整一下讓警告消失
例如點一下第一個警告
Fixed leading and trailing constraints may cause clipping, one of them should have a constant ≥ standard space.
Label 左邊直接越過 Image View 設定與 Content View 的距離
就會出現這樣的警告
點一下 Controller Scene 右邊的箭頭,會列出這個頁面的警告
點一下警告右邊的箭頭,會出現建議的解決方法
不過在這個例子,建議的方法都不好
要改成把 Image View 加上左邊、上方、寬、高的 Constraints 後
再把 Label 的左邊間距改成與 Image View 的距離即可
第二種警告,對於 Button 使用固定寬度可以會造成文 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift4] 更新為 Xcode9 後出現的 Auto Layout 警告<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2018-08-16 Thu. 01:29:19</div><hr color="#008080" />───────────────────
將 Xcode 從 8 更新到 9 後,在 Storyboard 可能會出現一堆警告
因為 Xcode 9 的 Auto Layout 變嚴格了
<div class="img" data-ori_w="634" data-ori_h="657" style="width:317px;height:329px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/kI1cXTa.png" alt="[圖]" /></div>
因為擔心文字如果使用不同語言可能會被裁切,或是跟其他元件重疊
如果確定不會用其他語言也可以不理會警告,或是找方法調整一下讓警告消失
例如點一下第一個警告
Fixed leading and trailing constraints may cause clipping, one of them should have a constant ≥ standard space.
<div class="img" data-ori_w="1403" data-ori_h="358" style="width:702px;height:179px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Zj3WQ13.png" alt="[圖]" /></div>
Label 左邊直接越過 Image View 設定與 Content View 的距離
就會出現這樣的警告
點一下 Controller Scene 右邊的箭頭,會列出這個頁面的警告
點一下警告右邊的箭頭,會出現建議的解決方法
<div class="img" data-ori_w="1470" data-ori_h="354" style="width:735px;height:177px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/UW3QPHP.png" alt="[圖]" /></div>
不過在這個例子,建議的方法都不好
要改成把 Image View 加上左邊、上方、寬、高的 Constraints 後
再把 Label 的左邊間距改成與 Image View 的距離即可
<div class="img" data-ori_w="1449" data-ori_h="583" style="width:725px;height:292px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/l5yCBQm.png" alt="[圖]" /></div>
第二種警告,對於 Button 使用固定寬度可以會造成文字裁切
Fixed width constraints may cause clipping.
<div class="img" data-ori_w="1480" data-ori_h="331" style="width:740px;height:166px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/AWqoStB.png" alt="[圖]" /></div>
建議的方法是將寬度設成大於等於
<div class="img" data-ori_w="840" data-ori_h="432" style="width:420px;height:216px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/FNuGGHD.png" alt="[圖]" /></div>
但這樣設定的話預覽的寬度會變很大
<div class="img" data-ori_w="940" data-ori_h="489" style="width:470px;height:245px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/H9FG5m6.png" alt="[圖]" /></div>
改成小於等於就可以了,雖然好像沒解決可能裁切的問題,但警告會消失
<div class="img" data-ori_w="928" data-ori_h="479" style="width:464px;height:240px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/X2VJag6.png" alt="[圖]" /></div>
對於 Label 使用固定寬度的問題,可以把 Lines 設為 0,警告就會消失了
<div class="img" data-ori_w="1529" data-ori_h="281" style="width:765px;height:141px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/99IoQdn.png" alt="[圖]" /></div>
參考
<div class="long_string"><a href="https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/%E7%82%BA%E4%BA%86%E6%88%91%E5%80%91%E5%A5%BD-xcode-9-%E7%9A%84-auto-layout-%E6%AA%A2%E6%9F%A5%E6%9B%B4%E5%9A%B4%E6%A0%BC%E4%BA%86-9a54a12f2278" target="_blank" rel="nofollow">為了我們好,Xcode 9 的 Auto Layout 檢查更嚴格了 - 彼得潘的 Swift iOS App 開發問題解答集</a></div>
<div class="long_string">StackOverflow <a href="https://stackoverflow.com/questions/45122691/xcode-9-fixed-width-constraints-may-cause-clipping-and-other-localization-wa" target="_blank" rel="nofollow">Xcode 9 - “Fixed Width Constraints May Cause Clipping” and Other Localization Warnings</a></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2018-08-16 01:29:19</span>
<span class="record">※ 編輯: Knuckles 時間: 2018-08-18 01:55:58</span></div></pre>
Knuckles
[AndroidStudio] 使用 Google 的 Firebase
http://disp.cc/b/11-avCO
2018-03-16T01:27:02+08:00
2018-03-16T01:27:02+08:00
Firebase 是 Google 出的一套整合 APP 各種工具的雲端開發平台
包含了 Analytics、Admob、錯誤訊息分析、後端資料儲存等等
官網有詳細介紹
https://firebase.google.com/
或是看介紹影片 (可以點設定選簡中字幕)
Introducing Firebase - YouTube
Firebase has all the tools you need to build a successful app. It helps you reach new users, keep them engaged, scale up an infrastructure to meet that deman...
這篇文章會記錄如何在 Android Studio 中整合 Firebase
例如我們在 Android Studio 開了一個新的專案
名稱為「TestFirebase」,套件名稱為「com.xxx.testfirebase」
在 Firebase 新增專案
在首頁點 Get Started 進入控制台
點新增專案
輸入專案名稱、地區,點 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [AndroidStudio] 使用 Google 的 Firebase<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2018-03-16 Fri. 01:27:03</div><hr color="#008080" />───────────────────
Firebase 是 Google 出的一套整合 APP 各種工具的雲端開發平台
包含了 Analytics、Admob、錯誤訊息分析、後端資料儲存等等
官網有詳細介紹
<a href="https://firebase.google.com/" target="_blank" rel="nofollow">https://firebase.google.com/</a>
或是看介紹影片 (可以點設定選簡中字幕)
<div style="background-color:#222; margin:.25em; padding:.25em; text-align:left;"><div class="quote_in"><a href="http://www.youtube.com/watch?v=O17OWyx08Cg" target="_blank" rel="nofollow"><strong>Introducing Firebase - YouTube</strong></a>
Firebase has all the tools you need to build a successful app. It helps you reach new users, keep them engaged, scale up an infrastructure to meet that deman...</div></div>
<div class="video" data-src="//www.youtube.com/embed/O17OWyx08Cg?autohide=1&"><img style="max-width:100%;" width="640" height="480" src="https://img.youtube.com/vi/O17OWyx08Cg/0.jpg" /><div class="play_btn"></div></div>
這篇文章會記錄如何在 Android Studio 中整合 Firebase
例如我們在 Android Studio 開了一個新的專案
名稱為「TestFirebase」,套件名稱為「com.xxx.testfirebase」
<span style="color:#00F000">在 Firebase 新增專案</span>
在首頁點 Get Started 進入控制台
<div class="img" data-ori_w="1140" data-ori_h="663" style="width:570px;height:332px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tirjigB.png" alt="[圖]" /></div>
點新增專案
<div class="img" data-ori_w="902" data-ori_h="822" style="width:451px;height:411px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oLJ4lnf.png" alt="[圖]" /></div>
輸入專案名稱、地區,點建立專案
<div class="img" data-ori_w="691" data-ori_h="613" style="width:346px;height:307px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/9nSoh88.png" alt="[圖]" /></div>
注意 Android 和 iOS 可共用同一個 Firebase 的專案
點「將 Firebase 加入您的 Android 應用程式」
<div class="img" data-ori_w="916" data-ori_h="482" style="width:458px;height:241px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/TVCem61.png" alt="[圖]" /></div>
輸入 Android Studio 專案的套件名稱與應用程式暱稱
<div class="img" data-ori_w="944" data-ori_h="791" style="width:472px;height:396px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/qjKgwGl.png" alt="[圖]" /></div>
應用程式暱稱為顯示在 Firebase 後台用的,
可加個 Android 用來跟 iOS 的做區別
下載 google-service.json 並加到 Android Studio 專案資料夾中的「app」資料夾下
<div class="img" data-ori_w="946" data-ori_h="918" style="width:473px;height:459px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/g6th0xg.png" alt="[圖]" /></div>
<div class="img" data-ori_w="370" data-ori_h="497" style="width:185px;height:249px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/WzmBiaD.png" alt="[圖]" /></div>
設定 Gradle 來載入 Firebase
<div class="img" data-ori_w="938" data-ori_h="965" style="width:469px;height:483px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/yF6BI0V.png" alt="[圖]" /></div>
修改專案的 build.gradle
在 dependencies 裡加上「classpath 'com.google.gms:google-services:3.2.0'」
<div class="img" data-ori_w="1222" data-ori_h="580" style="width:611px;height:290px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/MmmZQcq.png" alt="[圖]" /></div>
修改app的 build.gradle
在 dependencies 裡加上「compile 'com.google.firebase:firebase-core:11.8.0'」
在最後面加上「apply plugin: 'com.google.gms.google-services'」
<div class="img" data-ori_w="1267" data-ori_h="974" style="width:634px;height:487px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/robZO0C.png" alt="[圖]" /></div>
最後按一下編輯器上面出現的「Sync Now」
安裝完成
<div class="img" data-ori_w="1267" data-ori_h="850" style="width:634px;height:425px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Fqt3S1x.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2018-03-16 01:27:03</span></div></pre>
Knuckles
[AndroidStudio] 將專案上傳至 GitHub 與其他人共同開發
http://disp.cc/b/11-ar7U
2018-01-26T02:57:04+08:00
2023-11-07T16:30:21+08:00
延續上一篇 [AndroidStudio] 使用 Git 版本控制系統(VCS) - KnucklesNote板 - Disp BBS
已將專案使用 Git 做版本控制了
但這樣也只能自己用而已,想要讓多人共同開發的話要找個網站上傳
這篇要記錄如何將專案上傳至 GitHub 網站
GitHub 是現在最多人使用的程式碼代管網站
使用免費方案的話程式碼必需公開分享,無限制上傳數量
若付費的話可以只限團隊成員存取
在 GitHub 新增 Repository
如果還沒在 GitHub 註冊的話,先註冊一個帳號
帳號使用的方案選免費的就好
免費方案的限制就是上傳的程式碼都要設為公開
* 2020年後免費方案也可以設為私人了
在 Android Studio 中就可以直接新增 GitHub Repository
不用在 GitHub 網站中新增
點「VCS」/「Import into Version Control」/「Share Project on GitHub」
要先登入 GitHub,在 Auth Type 選「Password」
輸入 GitHub 註冊的帳號密碼後,按「Login」 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [AndroidStudio] 將專案上傳至 GitHub 與其他人共同開發<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2018-01-26 Fri. 02:57:04</div><hr color="#008080" />───────────────────
延續上一篇 <a href="https://disp.cc/b/KnucklesNote/11-aksj" target="_blank" rel="nofollow">[AndroidStudio] 使用 Git 版本控制系統(VCS) - KnucklesNote板 - Disp BBS</a>
已將專案使用 Git 做版本控制了
但這樣也只能自己用而已,想要讓多人共同開發的話要找個網站上傳
這篇要記錄如何將專案上傳至 <a href="https://github.com" target="_blank" rel="nofollow">GitHub</a> 網站
GitHub 是現在最多人使用的程式碼代管網站
使用免費方案的話程式碼必需公開分享,無限制上傳數量
若付費的話可以只限團隊成員存取
<span style="color:#00F000">在 GitHub 新增 Repository</span>
如果還沒在 <a href="http://github.com" target="_blank" rel="nofollow">GitHub</a> 註冊的話,先註冊一個帳號
<div class="img" data-ori_w="607" data-ori_h="271" style="width:607px;height:271px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/w1ulem9.png" alt="[圖]" /></div>
帳號使用的方案選免費的就好
<div class="img" data-ori_w="618" data-ori_h="604" style="width:618px;height:604px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/fqYeP14.png" alt="[圖]" /></div>
<s>免費方案的限制就是上傳的程式碼都要設為公開</s>
* 2020年後免費方案也可以設為私人了
在 Android Studio 中就可以直接新增 GitHub Repository
不用在 GitHub 網站中新增
點「VCS」/「Import into Version Control」/「Share Project on GitHub」
<div class="img" data-ori_w="905" data-ori_h="642" style="width:453px;height:321px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/wLYbZHj.png" alt="[圖]" /></div>
要先登入 GitHub,在 Auth Type 選「Password」
輸入 GitHub 註冊的帳號密碼後,按「Login」登入
<div class="img" data-ori_w="599" data-ori_h="310" style="width:300px;height:155px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/CQECzd1.png" alt="[圖]" /></div>
輸入要在 GitHub 上新增的 Repository 名稱後,點「Share」
<div class="img" data-ori_w="627" data-ori_h="332" style="width:314px;height:166px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/rlog1zi.png" alt="[圖]" /></div>
成功後會出現這個訊息
<div class="img" data-ori_w="448" data-ori_h="97" style="width:224px;height:49px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/movDvwl.png" alt="[圖]" /></div>
點一下上面的連結,就可以在 GitHub 網站上看到上傳好的專案了
<div class="img" data-ori_w="1402" data-ori_h="975" style="width:701px;height:488px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Y0hWiCp.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Push 將更新的程式碼上傳至 GitHub</span>
新增一段程式碼
<div class="highlight"><span></span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testPush</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Log</span><span class="p">.</span><span class="na">d</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span><span class="s">"測試用push上傳程式"</span><span class="p">)</span>
<span class="w"> </span><span class="p">}</span>
</div>
然後點「VCS」/「Commit Changes...」
將游標移至「Commit」按鈕上,點選下方出現的「Commit and Push...」
<div class="img" data-ori_w="1245" data-ori_h="803" style="width:623px;height:402px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/3e5XNZQ.png" alt="[圖]" /></div>
就會在 Commit 之後跳出 Push Commits 的視窗
選擇剛剛新增的 Commmit 後,點「Push」
<div class="img" data-ori_w="953" data-ori_h="370" style="width:477px;height:185px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/2oD0xDx.png" alt="[圖]" /></div>
到 GitHub 網站上看看,剛剛更新的程式碼已加上去了
<div class="img" data-ori_w="1399" data-ori_h="847" style="width:700px;height:424px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/8RODFrS.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Check Out 下載 GitHub 上的專案</span>
假設自己是另一個開發者,或者是使用另外一台電腦時
想要將之前上傳至 GitHub 的專案下載來修改的話
可以在 Android Studio 的起始畫面點選
「Check out project from Version Control」/「GitHub」
<div class="img" data-ori_w="517" data-ori_h="631" style="width:259px;height:316px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/OrrH78z.png" alt="[圖]" /></div>
或是在已開啟其他專案時,點選
「File」/「New」/「Project from Version Control」/「GitHub」
<div class="img" data-ori_w="940" data-ori_h="296" style="width:470px;height:148px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/UNq17Nr.png" alt="[圖]" /></div>
會登入 GitHub 帳號,可直接在這選取帳號下的 Repository
然後在下載後要儲存的專案資料夾名稱,設為「GitExample2」
點選「Clone」開始下載
<div class="img" data-ori_w="943" data-ori_h="278" style="width:472px;height:139px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/b05TWx9.png" alt="[圖]" /></div>
若出現錯誤訊息
「Unsupported Modules Detected: Compilation is not supported for following modules: GitExample2. Unfortunately you can't have non-Gradle Java modules and Android-Gradle modules in one project.」
<div class="img" data-ori_w="1734" data-ori_h="244" style="width:867px;height:122px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/NASOd4t.png" alt="[圖]" /></div>
這是因為有些檔案被列在 .gitignore 所以沒有一起上傳到專案裡
只要點一下 Details... 後,點「Remove Selected」,將不存在的檔案從專案中移除
<div class="img" data-ori_w="1098" data-ori_h="298" style="width:549px;height:149px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/fjvleKX.png" alt="[圖]" /></div>
然後點一下「Tools」/「Android」/「Sync Project with Gradle Files」即可
<div class="img" data-ori_w="560" data-ori_h="238" style="width:280px;height:119px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/9jgBPFf.png" alt="[圖]" /></div>
接著來修改一下程式後上傳看看
新增這段程式
<div class="highlight"><span></span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testNewUserPush</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Log</span><span class="p">.</span><span class="na">d</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span><span class="s">"測試用另一個使用者上傳程式"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
</div>
執行 Commit and Push
<div class="img" data-ori_w="1287" data-ori_h="782" style="width:644px;height:391px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/M6EpBBG.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Merge 將另一個使用者的更新合併進來</span>
回到原本的專案 GitExample
上傳另一段程式
<div class="highlight"><span></span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testOriginalUserPush</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Log</span><span class="p">.</span><span class="na">d</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span><span class="s">"測試用原本的使用者上傳程式"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
</div>
執行 Commit and Push
<div class="img" data-ori_w="1290" data-ori_h="779" style="width:645px;height:390px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/JTeGenU.png" alt="[圖]" /></div>
可以 Commit 但是在 Push 時會被拒絕
<div class="img" data-ori_w="686" data-ori_h="263" style="width:343px;height:132px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/NVoQJp8.png" alt="[圖]" /></div>
因為 GitHub 上的程式已經被另一個使用者在 GitExample2 的專案修改過了
點一下「Merge」後,可以選擇要使用自己的修改,還是別人的修改,或是合併雙方的修改
在這邊我們選擇點一下「Merge...」來合併雙方的修改
<div class="img" data-ori_w="726" data-ori_h="342" style="width:363px;height:171px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/HMhlYX0.png" alt="[圖]" /></div>
在 Merge 視窗可以看到左邊是在本機的修改,右邊是存在 GitHub 上的修改
中間是合併後的結果
<div class="img" data-ori_w="1750" data-ori_h="668" style="width:875px;height:334px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/7hvZxOl.png" alt="[圖]" /></div>
點一下左邊視窗的 >> 符號,與右邊視窗的 << 符號
將兩個板本的修改都加進合併後的結果,然後點「Apply」
<div class="img" data-ori_w="1750" data-ori_h="668" style="width:875px;height:334px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/d7HBsSu.png" alt="[圖]" /></div>
使用 Merge 將程式碼合併後,還要再執行 Push 將合併的結果上傳
點「VCS」/「Git」/「Push...」
<div class="img" data-ori_w="886" data-ori_h="1104" style="width:443px;height:552px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ntciaMZ.png" alt="[圖]" /></div>
<div class="img" data-ori_w="953" data-ori_h="370" style="width:477px;height:185px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/KMrnICC.png" alt="[圖]" /></div>
檢查 GitHub 上的程式碼已經是合併的板本了
Commit 的分支圖會像這樣
<div class="img" data-ori_w="472" data-ori_h="158" style="width:236px;height:79px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/omFyQqY.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2018-01-26 02:57:04</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-07 16:30:21 (台灣)</span></div></pre>
Knuckles
[AndroidStudio] 使用 Git 版本控制系統(VCS)
http://disp.cc/b/11-aksj
2017-11-14T21:22:34+08:00
2018-02-04T20:16:53+08:00
本文使用的 Android Studio 版本為 3.0.1
作業系統為 Windows 7 64-bit 版
在 Android Studio 中,內建的版本控制功能叫做 VCS (Version Control System)
支援很多種版本控制系統,我們要用其中的 Git 功能
Git 是目前最多人在使用的一種分散式版本控制系統
在本文會示範開一個新的專案,使用 Commit 產生一個記錄點
修改程式後再產生一個記錄點後,查詢兩個記錄點的差異
再來會使用分支(Branch)的功能產生兩組程式
分別修改兩組程式的內容後
再使用 Merge 的功能將兩種版本的程式合併
安裝 Git for Windows
在 Windows 下的 Android Studio 中,
要先安裝 Git for Windows
在 https://git-scm.com/download/
點選「Windows」
選擇 32-bit 或 64-bit 的版本
下載安裝時都使用預設選項即可
64-bit 版本安裝的資料夾為「C:\Program Files\Git\」
在 Android Studio ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [AndroidStudio] 使用 Git 版本控制系統(VCS)<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-11-14 Tue. 21:22:26</div><hr color="#008080" />───────────────────
本文使用的 Android Studio 版本為 3.0.1
作業系統為 Windows 7 64-bit 版
在 Android Studio 中,內建的版本控制功能叫做 VCS (Version Control System)
支援很多種版本控制系統,我們要用其中的 Git 功能
Git 是目前最多人在使用的一種分散式版本控制系統
在本文會示範開一個新的專案,使用 Commit 產生一個記錄點
修改程式後再產生一個記錄點後,查詢兩個記錄點的差異
再來會使用分支(Branch)的功能產生兩組程式
分別修改兩組程式的內容後
再使用 Merge 的功能將兩種版本的程式合併
<span style="color:#00F000">安裝 Git for Windows</span>
在 Windows 下的 Android Studio 中,
要先安裝 Git for Windows
在 <a href="https://git-scm.com/download/" target="_blank" rel="nofollow">https://git-scm.com/download/</a>
點選「Windows」
<div class="img" data-ori_w="1312" data-ori_h="507" style="width:656px;height:254px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/I3SuucE.png" alt="[圖]" /></div>
選擇 32-bit 或 64-bit 的版本
<div class="img" data-ori_w="923" data-ori_h="669" style="width:462px;height:335px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/FFe86rW.png" alt="[圖]" /></div>
下載安裝時都使用預設選項即可
64-bit 版本安裝的資料夾為「C:\Program Files\Git\」
<span style="color:#00F000">在 Android Studio 中設定 Git</span>
在「File」/「Settings」中,選擇「Version Control」的「Git」
然後將執行路徑設為「C:\Program Files\Git\cmd\git.exe」
<div class="img" data-ori_w="1336" data-ori_h="672" style="width:668px;height:336px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/kOAwGwB.png" alt="[圖]" /></div>
按一下旁邊的「Test」看看能不能執行成功
<div class="img" data-ori_w="339" data-ori_h="202" style="width:170px;height:101px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/13bC3Pr.png" alt="[圖]" /></div>
確認沒問題後按「ok」關閉設定
<span style="color:#00F000">開新專案,啟動並初始化 Git</span>
開啟要使用 Git 的專案,或是新增一個測試用的專案「GitExample」
接著點上方選單的「VCS」/「Enable Version Control Integration」
<div class="img" data-ori_w="442" data-ori_h="338" style="width:221px;height:169px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/e7SVUsZ.png" alt="[圖]" /></div>
要使用的版本管理系統選擇「Git」
<div class="img" data-ori_w="786" data-ori_h="222" style="width:393px;height:111px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/wLO21RX.png" alt="[圖]" /></div>
按確定後,就會在專案的目錄下加上一個隱藏資料夾「.git」
這個叫做 Git 的倉庫 Repository
各種版本更動的記錄都放在這裡面
<div class="img" data-ori_w="315" data-ori_h="483" style="width:158px;height:242px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/MpAVVEJ.png" alt="[圖]" /></div>
另外還會自動新增一個檔案「.gitignore」
可以在這設定不要加進版本控制的檔案
在 Android Studio 左邊的瀏覽視窗由「Android」切換為「Project」
然後點開「.gitignore」檔,注意是專案目錄下的,不是 app 目錄下的
<div class="img" data-ori_w="966" data-ori_h="761" style="width:483px;height:381px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/XQm7KCl.png" alt="[圖]" /></div>
檔案上方出現是否要安裝 plugins 的提示,點「Install plugins」
<div class="img" data-ori_w="841" data-ori_h="79" style="width:421px;height:40px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/MHANSi4.png" alt="[圖]" /></div>
(沒有的話可以在「File」/「Settings」的 Plugins 裡
點「Browse repositories」後搜尋 .ignore 安裝)
<div class="img" data-ori_w="1092" data-ori_h="782" style="width:546px;height:391px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/vtjpjxB.png" alt="[圖]" /></div>
安裝完後要重開 Android Studio
如果之後把 Git 的記錄弄壞了,或是想要刪掉全部重弄的話
可以在「File」/「Setting...」/「Version Control」
點一下 Git 的目錄後,點右邊的<span style="color:#F00000">–</span>刪掉
<div class="img" data-ori_w="1322" data-ori_h="278" style="width:661px;height:139px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/BiC071p.png" alt="[圖]" /></div>
然後將專案目錄下的 .git 資料夾刪除
就可以回復成一開始沒有版本記錄的狀態了
<span style="color:#00F000">第一次 Commit</span>
將左邊瀏覽視窗由「Project」換回「Android」
在設定了版本控制後,可以看到在專案中的檔案名稱都變紅色了
代表這些檔案還沒有加進版本控制
<div class="img" data-ori_w="1254" data-ori_h="430" style="width:627px;height:215px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/UdNBJqD.png" alt="[圖]" /></div>
可以使用 Git 的 Add 將檔案加入版本控制,檔名就會變成綠色
或是直接使用 Commit 將檔案加入版本控制並產生一個記錄點
點一下「VCS」/「Commit Changes...」
<div class="img" data-ori_w="440" data-ori_h="273" style="width:220px;height:137px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/muYVHse.png" alt="[圖]" /></div>
或是點工具列上的按鈕
<div class="img" data-ori_w="320" data-ori_h="58" style="width:160px;height:29px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/IDyVmU9.png" alt="[圖]" /></div>
勾選「Unversioned Files 39 files」將所有檔案加進版本控制
在「Commit Message」輸入註解「第一次 Commit」後,點「Commit」
<div class="img" data-ori_w="1248" data-ori_h="827" style="width:624px;height:414px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/J6zUe11.png" alt="[圖]" /></div>
若出現這個視窗,先不管警告訊息,點「Commit」
<div class="img" data-ori_w="408" data-ori_h="228" style="width:204px;height:114px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/bI1FPJg.png" alt="[圖]" /></div>
若出現這個視窗,輸入帳號與E-mail,建立一個 Git 使用者
<div class="img" data-ori_w="560" data-ori_h="277" style="width:280px;height:139px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/aEPMAn0.png" alt="[圖]" /></div>
若出現這個視窗,問要不要把 .idea\vcs.xml 加進 Git,點「No」
<div class="img" data-ori_w="715" data-ori_h="245" style="width:358px;height:123px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/qGfjRrY.png" alt="[圖]" /></div>
Commit 完成後,檔名就會從紅色變回白色了
<span style="color:#00F000">修改程式後再次 Commit</span>
修改 MainActivity.java 看看,隨便加個兩行程式
<div class="img" data-ori_w="1135" data-ori_h="458" style="width:568px;height:229px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/yAHcmpP.png" alt="[圖]" /></div>
可以看到檔名變成藍色了,代表這個檔案從上次 Commit 後有修改過
新增的兩行程式前也會看到多了綠色區塊,
代表這兩行是新增的
再按一次「VCS」/「Commit Changes...」
<div class="img" data-ori_w="1202" data-ori_h="814" style="width:601px;height:407px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Uv2VTlk.png" alt="[圖]" /></div>
可以看到這次只有一個檔案需要 Commit
而且下面會將修改的部份標記出來
輸入註解後按 Commit
要查詢檔案的修改過程的話
先點一下想要看的檔案,例如 MainActivity.java
然後點「VCS」/「Local History」/「Show History」
<div class="img" data-ori_w="614" data-ori_h="111" style="width:307px;height:56px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/1tTd4rP.png" alt="[圖]" /></div>
<div class="img" data-ori_w="1573" data-ori_h="534" style="width:787px;height:267px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/zSfFsmk.png" alt="[圖]" /></div>
想要回復某次的修改的話,可以在右邊顯示的程式碼差異處,點中間的 >> 符號
<div class="img" data-ori_w="1001" data-ori_h="106" style="width:501px;height:53px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/THtPBUn.png" alt="[圖]" /></div>
或是對左邊的某時間點的檔案點右鍵,選「Revert」
<div class="img" data-ori_w="426" data-ori_h="235" style="width:213px;height:118px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/G4gk4mq.png" alt="[圖]" /></div>
就可以將程式碼回復到想要的時間點了
有使用版本控制後,下方視窗的頁籤會多一個「Version Control」
切換到「Version Control」裡的「Log」可查看 Commit 記錄點的分支圖
<div class="img" data-ori_w="1378" data-ori_h="479" style="width:689px;height:240px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ibIum8y.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Reset 回復至某次的 Commit 記錄點</span>
如果 Commit 後發現註解寫錯,或是程式碼還有地方加進去
想要刪除這次的 Commit 的話,可以使用 Reset
Reset 還有各種不同的模式,先記得
--soft 代表程式碼不變,只將 Commit 點刪除 (想要再改一下重新 Commit 的時候用)
--hard 代表除了將 Commit 點刪除,還會將程式碼還原 (程式碼也要重寫的時候用)
以下測試我們再加一個 Commit 點,然後使用 Reset 回復到上一個點
在程式中加入一行
<div class="highlight"><span></span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">test2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"測試 Reset"</span><span class="p">;</span>
</div>
然後執行 Commit,註解輸入「測試 Reset」
現在在分支圖可以看到有三個 Commit 記錄點了
<div class="img" data-ori_w="743" data-ori_h="199" style="width:372px;height:100px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/kLJnRg3.png" alt="[圖]" /></div>
此時我們後悔剛剛加上的第三個 Commit 點了,
可以在第二個 Commit 點按右鍵,選「Reset Current Branch to Here...」
<div class="img" data-ori_w="572" data-ori_h="379" style="width:286px;height:190px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/viYP1df.png" alt="[圖]" /></div>
出現四個選項,我們選「Soft」就好
<div class="img" data-ori_w="678" data-ori_h="508" style="width:339px;height:254px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ycXevc9.png" alt="[圖]" /></div>
執行完 Reset 後,在分支圖可以看到變回只有兩個 Commit 點了
並且程式中的 String test2 = "測試 Reset"; 還在,
若是 Reset 選擇用「Hard」的話,那行程式就會被刪除了
<span style="color:#00F000">新增分支 Branch</span>
當想要測試新功能,但又怕把本來的程式弄壞
希望隨時可以換回本來的程式
這時就可以使用分支的功能
在分支圖上對最後一次 Commit 的點按右鍵,選「New Branch...」
<div class="img" data-ori_w="620" data-ori_h="296" style="width:310px;height:148px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/vgBCybC.png" alt="[圖]" /></div>
輸入分支名稱「TestBranch」,勾選「Checkout branch」
<div class="img" data-ori_w="590" data-ori_h="247" style="width:295px;height:124px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Z21LDvs.png" alt="[圖]" /></div>
可以看到目前這個 Commit 點同時為 master 與 TestBranch 兩個分支
<div class="img" data-ori_w="736" data-ori_h="146" style="width:368px;height:73px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/Wh0q9u2.png" alt="[圖]" /></div>
在右下角可以看到有顯示「Git: TestBranch」
代表目前是在 TestBranch 這個分支,點一下後有選單可切換至別的分支
<div class="img" data-ori_w="670" data-ori_h="226" style="width:335px;height:113px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/qkAiwUe.png" alt="[圖]" /></div>
修改程式,隨便加個函數 testBranch() 代表在這個分支新增的測試功能
<div class="highlight"><span></span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testBranch</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Log</span><span class="p">.</span><span class="na">d</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span><span class="s">"測試分支功能"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
</div>
然後執行 Commit
<div class="img" data-ori_w="1407" data-ori_h="774" style="width:704px;height:387px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/euSHnoi.png" alt="[圖]" /></div>
可以看到分支圖變成這樣,master 分支留在了上一個 Commit 點
<div class="img" data-ori_w="740" data-ori_h="119" style="width:370px;height:60px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/lT7QEfp.png" alt="[圖]" /></div>
這時想要回去 master 分支寫主要的功能
點右下角的「Git: TestBranch」,選分支「master」,選「Checkout」
<div class="img" data-ori_w="538" data-ori_h="257" style="width:269px;height:129px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/rVtlxdt.png" alt="[圖]" /></div>
可以看到右下角改為顯示「Git: master」,代表現在是在分支 master
而分支圖變成這樣
<div class="img" data-ori_w="736" data-ori_h="114" style="width:368px;height:57px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/tc3keHb.png" alt="[圖]" /></div>
黃色的 HEAD 符號移到了分支 master
此時程式又恢復到第二次 Commit 時的狀態
修改程式,加上一個函數 testMaster()
<div class="highlight"><span></span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testMaster</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Log</span><span class="p">.</span><span class="na">d</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span><span class="s">"測試 master 功能"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
</div>
然後執行 Commit
<div class="img" data-ori_w="1407" data-ori_h="774" style="width:704px;height:387px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/yGlk86x.png" alt="[圖]" /></div>
分支圖會變成這樣
<div class="img" data-ori_w="813" data-ori_h="152" style="width:407px;height:76px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/UcrTYP2.png" alt="[圖]" /></div>
從第二個 Commit 點後產生了分歧
<span style="color:#00F000">使用 Merge 合併分支</span>
當分支 TestBranch 的測試功能寫好後,想要將修改的內容合併回分支 master
先回到分支 master 後執行一次 commit
然後點選單的「VCS」/「Git」/「Merge Changes...」
<div class="img" data-ori_w="887" data-ori_h="749" style="width:444px;height:375px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/kF4MIUk.png" alt="[圖]" /></div>
將要合併的分支 TestBranch 打勾後,點「Merge」
<div class="img" data-ori_w="755" data-ori_h="462" style="width:378px;height:231px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ouC77V4.png" alt="[圖]" /></div>
會發現程式出現了衝突,
可選擇衝突的地方要使用目前 master 的版本(Accept Yours)
或是使用分支 TestBranch 的版本(Accept Theirs)
或是使用 Merge 將兩個版本的內容都加進來
<div class="img" data-ori_w="1338" data-ori_h="314" style="width:669px;height:157px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/SKVktEL.png" alt="[圖]" /></div>
我們想要將兩個版本寫的函數都加進來,所以點「Merge」
接著會顯示三個程式版本,左邊是目前 master 的,右邊是分支 TestBranch 的
中間是兩個分支合併後的
<div class="img" data-ori_w="1750" data-ori_h="750" style="width:875px;height:375px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/BhdNP5C.png" alt="[圖]" /></div>
先點一下上方的「Apply All Non-Conflicting Changes」按鈕
將兩邊沒衝突的修改加進來
<div class="img" data-ori_w="430" data-ori_h="110" style="width:215px;height:55px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/SS2thmm.png" alt="[圖]" /></div>
接著在兩邊標了紅色衝突的程式,都點一下代表 Accept 的箭頭加進來
<div class="img" data-ori_w="1750" data-ori_h="750" style="width:875px;height:375px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/LuUuXL0.png" alt="[圖]" /></div>
此時會顯示衝突都解決了,點「Apply」結束 Merge
<div class="img" data-ori_w="1750" data-ori_h="750" style="width:875px;height:375px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/pJLtTYW.png" alt="[圖]" /></div>
結束 Merge 後,右下角的分支名稱會顯示「Git: Merging master」
要再執行一次 Commit 做個記錄
執行完 Commit 後,右下角顯示「Git: master」
在左下角 Version Control 的 Log 視窗
就會看到兩條分支線合併為一個點了
<div class="img" data-ori_w="808" data-ori_h="268" style="width:404px;height:134px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/bGRkXE7.png" alt="[圖]" /></div>
參考
<a href="https://litotom.com/2016/05/09/%E5%9C%A8android-studio%E4%B8%AD%E5%8F%96%E5%BE%97github%E5%B0%88%E6%A1%88/" target="_blank" rel="nofollow">https://litotom.com/2016/05/09/在android-studio中取得github專案/</a>
<a href="https://www.jianshu.com/p/885786074e96" target="_blank" rel="nofollow">介绍Android Studio使用Git最详细的文章</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-11-14 21:22:26</span>
<span class="record">※ 編輯: Knuckles 時間: 2018-02-04 20:16:54</span></div></pre>
Knuckles
[Xcode][Swift3] 加入 Facebook Audience Network 原生廣告
http://disp.cc/b/11-aeBf
2017-09-12T04:35:14+08:00
2018-09-12T02:06:42+08:00
在 FB 新增一個廣告版位
先到 FB 廣告的官方說明文件
https://developers.facebook.com/docs/audience-network
建議先看一下新手指南的 5 步驟設定指南
建立廣告版位
點「立即開始」後,新增或進入已有的企業管理平台
進入營利管理工具,選擇或建立一個資產
資產名稱就設定為 APP 的名稱即可,例如 Disp BBS
在資產中新增平台,這邊新增「iOS 應用程式」
每個平台中可以建立四個廣告空間,預設已經建立好一個了
修改一下廣告空間的名稱,例如修改為「iOS 熱門文章」
在廣告空間中新增版位,選擇新增「原生橫幅」
取得版位編號,後面在程式中會用到
在 Xcode 加入 Audience Network SDK
這邊使用 CocoaPods 來安裝
CocoaPods 的使用方法可參考 這篇
在 Podfile 加上 pod 'FBAudienceNetwork'
在專案目錄執行 pod install
因為 Audience Network SDK 是使用 Objective-C 寫的,
要在 Swift 專案 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加入 Facebook Audience Network 原生廣告<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-09-12 Tue. 04:34:29</div><hr color="#008080" />───────────────────
<span style="color:#00F000">在 FB 新增一個廣告版位</span>
先到 FB 廣告的官方說明文件
<a href="https://developers.facebook.com/docs/audience-network" target="_blank" rel="nofollow">https://developers.facebook.com/docs/audience-network</a>
<div class="img" data-ori_w="1345" data-ori_h="657" style="width:673px;height:329px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/g9NkBHx.png" alt="[圖]" /></div>
建議先看一下新手指南的 <a href="https://www.facebook.com/help/publisher/1195459597167215/" target="_blank" rel="nofollow">5 步驟設定指南</a>
建立廣告版位
點「立即開始」後,新增或進入已有的企業管理平台
進入營利管理工具,選擇或建立一個資產
資產名稱就設定為 APP 的名稱即可,例如 Disp BBS
<div class="img" data-ori_w="962" data-ori_h="626" style="width:481px;height:313px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ho18Dd7.png" alt="[圖]" /></div>
在資產中新增平台,這邊新增「iOS 應用程式」
<div class="img" data-ori_w="965" data-ori_h="902" style="width:483px;height:451px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/ZRsD6Pr.png" alt="[圖]" /></div>
每個平台中可以建立四個廣告空間,預設已經建立好一個了
修改一下廣告空間的名稱,例如修改為「iOS 熱門文章」
在廣告空間中新增版位,選擇新增「原生橫幅」
<div class="img" data-ori_w="982" data-ori_h="1157" style="width:491px;height:579px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/XYo7shI.png" alt="[圖]" /></div>
取得版位編號,後面在程式中會用到
<div class="img" data-ori_w="1695" data-ori_h="522" style="width:848px;height:261px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/falPu76.png" alt="[圖]" /></div>
<span style="color:#00F000">在 Xcode 加入 Audience Network SDK</span>
這邊使用 CocoaPods 來安裝
CocoaPods 的使用方法可參考 <a href="https://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">這篇</a>
在 Podfile 加上 pod 'FBAudienceNetwork'
在專案目錄執行 pod install
因為 Audience Network SDK 是使用 Objective-C 寫的,
要在 Swift 專案中使用 Objective-C 的話,要加上 BridgingHeader
如果之前沒加過的話,點 command+n 新增檔案,選「Header File」
檔案名稱輸入「BridgingHeader」
在專案設定的「Build Settings」,選「All」,
在右邊的搜尋框輸入「bridging」,
在下面出現的 Objective-C Bridging Header 輸入「$(PROJECT_NAME)/BridgingHeader.h」
<div class="img" data-ori_w="1459" data-ori_h="306" style="width:730px;height:153px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6zazBme.png" alt="[圖]" /></div>
修改 BridgingHeader.h
在 #define ... 與 #endif 之間加上
<div class="highlight"><span></span><span class="cp">#import <FBAudienceNetwork/FBAudienceNetwork.h></span>
</div>
<span style="color:#00F000">在 TableViewController 中插入 FB 原生廣告範本</span>
Audience Network 有提供專門給 TableViewController 使用的類別
<a href="https://developers.facebook.com/docs/reference/ios/current/class/FBNativeAdsManager/" target="_blank" rel="nofollow">FBNativeAdsManager</a> 和 <a href="https://developers.facebook.com/docs/reference/ios/current/class/FBNativeAdTableViewCellProvider/" target="_blank" rel="nofollow">FBNativeAdTableViewCellProvider</a>
可以很方便的在列表加上多個相同大小的廣告
例如我們要在熱門文章頁加上廣告,
所以要修改熱門文章頁的類別 HotTextViewController
先加上兩個成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">fbAdsManager</span><span class="p">:</span> <span class="n">FBNativeAdsManager</span><span class="p">!</span>
<span class="kd">var</span> <span class="nv">fbAdsCellProvider</span><span class="p">:</span> <span class="n">FBNativeAdTableViewCellProvider</span><span class="p">!</span>
</div>
新增用來初始化 fbAdsManager 的函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">initAdsManager</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">fbAdsManager</span> <span class="p">==</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="n">fbAdsManager</span> <span class="p">=</span> <span class="n">FBNativeAdsManager</span><span class="p">(</span><span class="n">placementID</span><span class="p">:</span> <span class="s">"PLACEMENT_ID"</span><span class="p">,</span> <span class="n">forNumAdsRequested</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">fbAdsManager</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">fbAdsManager</span><span class="p">.</span><span class="n">loadAds</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
在 PLACEMENT_ID 填入之前申請到的版位編號
forNumAdsRequested 代表要載入幾個廣告,這邊我們只要一個就好
fbAdsManager.delegate = self 委託函數給目前的類別,這行現在會出現錯誤,因為還沒在類別加上協定
fbAdsManager.loadAds() 預先載入廣告
在 viewVillAppear() 中執行 initAdsManager()
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewWillAppear</span><span class="p">(</span><span class="kc">_</span> <span class="n">animated</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewWillAppear</span><span class="p">(</span><span class="n">animated</span><span class="p">)</span>
<span class="c1">// ...</span>
<span class="n">initAdsManager</span><span class="p">()</span>
<span class="p">}</span>
</div>
在 class HotTextViewController: UITableViewController, 後面加上
兩個協定:FBNativeAdDelegate, FBNativeAdsManagerDelegate
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">HotTextViewController</span><span class="p">:</span> <span class="bp">UITableViewController</span><span class="p">,</span> <span class="n">FBNativeAdDelegate</span><span class="p">,</span> <span class="n">FBNativeAdsManagerDelegate</span> <span class="p">{</span>
</div>
協定 FBNativeAdsManagerDelegate 會需要加上兩個 Delegate 函數: nativeAdsLoaded(), nativeAdsFailedToLoadWithError()
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - FBNativeAdsManagerDelegate</span>
<span class="kd">func</span> <span class="nf">nativeAdsLoaded</span><span class="p">()</span> <span class="p">{</span>
<span class="n">fbAdsCellProvider</span> <span class="p">=</span> <span class="n">FBNativeAdTableViewCellProvider</span><span class="p">(</span><span class="n">manager</span><span class="p">:</span> <span class="n">fbAdsManager</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">FBNativeAdViewType</span><span class="p">.</span><span class="n">genericHeight100</span><span class="p">)</span>
<span class="n">fbAdsCellProvider</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeAdsFailedToLoadWithError</span><span class="p">(</span><span class="kc">_</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
</div>
函數 nativeAdsLoaded() 用來確認廣告是否有載入了
使用 FBNativeAdViewType.genericHeight100 代表設定廣告高度為 100
有四個高度可以選擇:100, 120, 300 和 400 pt
使用 tableView.reloadData() 更新 tableView 的顯示資料
函數 nativeAdsFailedToLoadWithError() 用來在廣告載入失敗時顯示錯誤訊息
協定 FBNativeAdDelegate 的 Delegate 函數為選用的,
只要打開頭 nativeAd 就會列出可以用的函數,例如
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">nativeAdDidClick</span><span class="p">(</span><span class="kc">_</span> <span class="n">nativeAd</span><span class="p">:</span> <span class="n">FBNativeAd</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"Ad tapped: </span><span class="si">\(</span><span class="n">nativeAd</span><span class="p">.</span><span class="n">title</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
</div>
可以在點擊廣告時,顯示廣告的標題
能夠載入廣告後,再來是調整 TableView 的顯示
先調整 TableView 會顯示幾個 Row
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">dataNum</span> <span class="p">=</span> <span class="mi">10</span> <span class="c1">//原本熱門文章的筆數</span>
<span class="k">if</span> <span class="n">fbAdsCellProvider</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">dataNum</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">dataNum</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
設定 TableView 中每個 Row 要顯示的 Cell
例如要在第六列插入一個廣告
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">fbAdsCellProvider</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="o">&&</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="p">==</span> <span class="mi">5</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fbAdsCellProvider</span><span class="p">.</span><span class="n">tableView</span><span class="p">(</span><span class="n">tableView</span><span class="p">,</span> <span class="n">cellForRowAt</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// 原本用來顯示熱門文章的程式</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
執行看看,在第六列有出現廣告了
<div class="img" data-ori_w="524" data-ori_h="785" style="width:262px;height:393px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/SoJoBPa.png" alt="[圖]" /></div>
<span style="color:#00F000">修改廣告的樣式</span>
修改 Delegate 函數 nativeAdsLoaded()
將 fbAdsCellProvider = FBNativeAdTableViewCellProvider(manager: fbAdsManager, for: FBNativeAdViewType.genericHeight100)
改為多傳入一個 attributes 變數,例如
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">attributes</span> <span class="p">=</span> <span class="n">FBNativeAdViewAttributes</span><span class="p">()</span>
<span class="n">attributes</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">black</span>
<span class="n">attributes</span><span class="p">.</span><span class="n">titleColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">(</span><span class="n">red</span><span class="p">:</span> <span class="mh">0x39</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">green</span><span class="p">:</span> <span class="mh">0x91</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">blue</span><span class="p">:</span> <span class="mh">0xFF</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">attributes</span><span class="p">.</span><span class="n">descriptionColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">white</span>
<span class="n">attributes</span><span class="p">.</span><span class="n">buttonColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">(</span><span class="n">red</span><span class="p">:</span> <span class="mh">0x09</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">green</span><span class="p">:</span> <span class="mh">0x50</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">blue</span><span class="p">:</span> <span class="mh">0xD0</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">attributes</span><span class="p">.</span><span class="n">buttonTitleColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">white</span>
<span class="n">fbAdsCellProvider</span> <span class="p">=</span> <span class="n">FBNativeAdTableViewCellProvider</span><span class="p">(</span><span class="n">manager</span><span class="p">:</span> <span class="n">fbAdsManager</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">FBNativeAdViewType</span><span class="p">.</span><span class="n">genericHeight100</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">attributes</span><span class="p">)</span>
</div>
就可以修改背景色、標題和說明文字的顏色、按鈕的背景色和文字顏色
執行結果像這樣
<div class="img" data-ori_w="524" data-ori_h="584" style="width:262px;height:292px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/0TJPdCy.png" alt="[圖]" /></div>
<span style="color:#00F000">在實機上使用測試廣告</span>
如果不是用模擬器,而是將App裝到實體裝置上的話
必需要加上測試廣告的設定
在載入廣告前加上以下程式碼
例如前面的例子是加在函數 initAdsManager() 最前面
<div class="highlight"><span></span> <span class="n">FBAdSettings</span><span class="p">.</span><span class="n">setLogLevel</span><span class="p">(</span><span class="n">FBAdLogLevel</span><span class="p">.</span><span class="n">log</span><span class="p">)</span>
<span class="n">FBAdSettings</span><span class="p">.</span><span class="n">addTestDevice</span><span class="p">(</span><span class="s">"HASHED_ID"</span><span class="p">)</span>
</div>
在實機執行後,在 Console 視窗找到以下訊息
<div class="highlight"><span></span>When testing your app with Facebook ad units,
you must specify the device hashed ID to ensure the delivery of test ads,
add the following code before loading an ad: <code class="code-inline">[FBAdSettings addTestDevice:@"HASHED_ID"]</code>
Test mode device hash: bd675f960298a92003630d76fa612b1706b745ab
</div>
將上方顯示的 device hash 填入程式中的 "HASHED_ID"
在重新執行一次,就會出現測試廣告了
之後如果不想要 Console 出現一大堆 FB 廣告的 log 記錄
可以將 setLogLevel(FBAdLogLevel.log) 改為
<div class="highlight"><span></span> <span class="c1">// 不要顯示 FB 廣告的 log 記錄</span>
<span class="n">FBAdSettings</span><span class="p">.</span><span class="n">setLogLevel</span><span class="p">(</span><span class="n">FBAdLogLevel</span><span class="p">.</span><span class="kr">none</span><span class="p">)</span>
<span class="c1">// 或是只顯示錯訊訊息</span>
<span class="n">FBAdSettings</span><span class="p">.</span><span class="n">setLogLevel</span><span class="p">(</span><span class="n">FBAdLogLevel</span><span class="p">.</span><span class="n">error</span><span class="p">)</span>
</div>
想要取消實機為測試裝置的話,將 addTestDevice() 改為
<div class="highlight"><span></span> <span class="n">FBAdSettings</span><span class="p">.</span><span class="n">clearTestDevice</span><span class="p">(</span><span class="s">"HASHED_ID"</span><span class="p">)</span>
</div>
將 "HASHED_ID" 改為之前取得的 device hash
<span style="color:#00F000">使用完全客制化的原生廣告</span>
如果不想使用FB的原生廣告範本,要自己拉所有元件的話
先按 command+n 新增一個 TableViewCell 的類別
Class: NativeAdsCell
Subclass of: UITableViewCell
修改新增的檔案 NativeAdsCell.swift
加入這些成員變數
<div class="highlight"><span></span> <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adContainerView</span><span class="p">:</span> <span class="bp">UIView</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adTitle</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adBody</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adIconImage</span><span class="p">:</span> <span class="n">FBAdIconView</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adActionBtn</span><span class="p">:</span> <span class="n">FBAdChoicesView</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adSocialContext</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">adCoverMediaView</span><span class="p">:</span> <span class="n">FBMediaView</span><span class="p">!</span>
</div>
修改 Storyboard
在 TableView 增加一個 Prototype Cell
將 Identifier 設為「FBNativeAdsCell」
Custome Class 設為「NativeAdsCell」
先拉一個與 Cell 相同大小的 adContainerView (UIView)
然後將其他廣告元件拉到這個新增的 adContainerView
再將 Cell 的 Outlet 連結到這些元件
像這樣:
<div class="img" data-ori_w="1623" data-ori_h="709" style="width:812px;height:355px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/wnx51BT.png" alt="[圖]" /></div>
其中 adSocialContext 與 adCoverMediaView 因為沒位子放了所以略過不放
在 adContainerView 的屬性檢視器將「Hidden」打勾
修改 HotTextViewController.swift
在 class 後面加上協定 FBNativeAdDelegate,例如
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">HotTextViewController</span><span class="p">:</span> <span class="bp">UITableViewController</span><span class="p">,</span> <span class="n">FBNativeAdDelegate</span> <span class="p">{</span>
</div>
新增成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">fbNativeAd</span><span class="p">:</span> <span class="n">FBNativeAd</span><span class="p">!</span>
</div>
新增自訂的成員函數 loadFBNativeAd()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">loadFBNativeAd</span><span class="p">()</span> <span class="p">{</span>
<span class="n">fbNativeAd</span> <span class="p">=</span> <span class="n">FBNativeAd</span><span class="p">(</span><span class="n">placementId</span><span class="p">:</span> <span class="s">"YOUR_PLACEMENT_ID"</span><span class="p">)</span>
<span class="n">fbNativeAd</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">fbNativeAd</span><span class="p">.</span><span class="n">loadAd</span><span class="p">()</span>
</div>
將 YOUR_PLACEMENT_ID 改為之前申請的版位編號
在 viewDidLoad() 中執行 loadFBNativeAd()
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="n">loadFBNativeAd</span><span class="p">()</span>
<span class="p">}</span>
</div>
新增 FBNativeAdDelegate 的兩個 Delegate 函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - FBNativeAdDelegate</span>
<span class="kd">func</span> <span class="nf">nativeAdDidLoad</span><span class="p">(</span><span class="kc">_</span> <span class="n">nativeAd</span><span class="p">:</span> <span class="n">FBNativeAd</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">fbNativeAd</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">fbNativeAd</span><span class="p">.</span><span class="n">unregisterView</span><span class="p">()</span>
<span class="p">}</span>
<span class="kc">self</span><span class="p">.</span><span class="n">fbNativeAd</span> <span class="p">=</span> <span class="n">nativeAd</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeAd</span><span class="p">(</span><span class="kc">_</span> <span class="n">nativeAd</span><span class="p">:</span> <span class="n">FBNativeAd</span><span class="p">,</span> <span class="n">didFailWithError</span> <span class="n">error</span><span class="p">:</span> <span class="n">Error</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
</div>
廣告載入後會執行 nativeAdDidLoad()
將取得的廣告內容存入成員變數 fbNativeAd
然後重載 tableView 的顯示內容
修改顯示 cell 內容的函數 tableView(_:cellForrowAt:)
例如要在第6列顯示一個原生廣告的話,在前面加上
<div class="highlight"><span></span> <span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="p">==</span> <span class="mi">5</span> <span class="p">{</span> <span class="c1">// 執行自訂的成員函數 getFBAdsCell()</span>
<span class="k">return</span> <span class="n">getFBAdsCell</span><span class="p">(</span><span class="n">cellForRowAt</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="p">}</span>
</div>
新增一個成員函數 getFBAdsCell()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">getFBAdsCell</span><span class="p">(</span><span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="n">NativeAdsCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"FBNativeAdsCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">NativeAdsCell</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">nativeAd</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">fbNativeAd</span> <span class="p">{</span>
<span class="c1">// 設定廣告標題</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adTitle</span><span class="p">.</span><span class="n">text</span> <span class="p">=</span> <span class="n">nativeAd</span><span class="p">.</span><span class="n">advertiserName</span>
<span class="c1">// 設定廣告文本(如果有的話).</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">body</span> <span class="p">=</span> <span class="n">nativeAd</span><span class="p">.</span><span class="n">bodyText</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adBody</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="n">body</span>
<span class="p">}</span>
<span class="c1">// 設定 call-to-action 按鈕的標題</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adActionBtn</span><span class="p">.</span><span class="n">setTitle</span><span class="p">(</span><span class="n">nativeAd</span><span class="p">.</span><span class="n">callToAction</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">UIControlState</span><span class="p">.</span><span class="n">normal</span><span class="p">)</span>
<span class="c1">// 載入並顯示廣告圖片</span>
<span class="n">nativeAd</span><span class="p">.</span><span class="n">icon</span><span class="p">?.</span><span class="n">loadAsync</span><span class="p">(</span><span class="n">block</span><span class="p">:</span> <span class="p">{</span> <span class="p">(</span><span class="n">iconImage</span><span class="p">)</span> <span class="k">in</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">image</span> <span class="p">=</span> <span class="n">iconImage</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adIconImage</span><span class="p">.</span><span class="n">image</span> <span class="p">=</span> <span class="n">image</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="c1">// 設定 social context 字串 (如果有的話)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">socialContext</span> <span class="p">=</span> <span class="n">nativeAd</span><span class="p">.</span><span class="n">socialContext</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adSocialContext</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="n">socialContext</span>
<span class="p">}</span>
<span class="c1">// 載入媒體圖片 (如果有的話)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">adCoverMediaView</span> <span class="p">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">adCoverMediaView</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">coverMediaView</span> <span class="p">=</span> <span class="n">FBMediaView</span><span class="p">(</span><span class="n">frame</span><span class="p">:</span> <span class="n">adCoverMediaView</span><span class="p">.</span><span class="n">frame</span><span class="p">)</span>
<span class="n">coverMediaView</span><span class="p">.</span><span class="n">nativeAd</span> <span class="p">=</span> <span class="n">nativeAd</span>
<span class="n">coverMediaView</span><span class="p">.</span><span class="n">clipsToBounds</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">coverMediaView</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// 設定 AdChoices view</span>
<span class="kd">let</span> <span class="nv">adChoicesView</span> <span class="p">=</span> <span class="n">FBAdChoicesView</span><span class="p">(</span><span class="n">nativeAd</span><span class="p">:</span> <span class="n">nativeAd</span><span class="p">)</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">adChoicesView</span><span class="p">)</span>
<span class="c1">// 加上四個 Constraints,大小20x20 固定在右上角</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addConstraint</span><span class="p">(</span><span class="bp">NSLayoutConstraint</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="n">adChoicesView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">top</span><span class="p">,</span> <span class="n">relatedBy</span><span class="p">:</span> <span class="p">.</span><span class="bp">equal</span><span class="p">,</span> <span class="n">toItem</span><span class="p">:</span> <span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">top</span><span class="p">,</span> <span class="n">multiplier</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">constant</span><span class="p">:</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addConstraint</span><span class="p">(</span><span class="bp">NSLayoutConstraint</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="n">adChoicesView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">trailing</span><span class="p">,</span> <span class="n">relatedBy</span><span class="p">:</span> <span class="p">.</span><span class="bp">equal</span><span class="p">,</span> <span class="n">toItem</span><span class="p">:</span> <span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">trailing</span><span class="p">,</span> <span class="n">multiplier</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">constant</span><span class="p">:</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addConstraint</span><span class="p">(</span><span class="bp">NSLayoutConstraint</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="n">adChoicesView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">relatedBy</span><span class="p">:</span> <span class="p">.</span><span class="bp">equal</span><span class="p">,</span> <span class="n">toItem</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">notAnAttribute</span><span class="p">,</span> <span class="n">multiplier</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">constant</span><span class="p">:</span> <span class="mi">20</span><span class="p">))</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addConstraint</span><span class="p">(</span><span class="bp">NSLayoutConstraint</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="n">adChoicesView</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">relatedBy</span><span class="p">:</span> <span class="p">.</span><span class="bp">equal</span><span class="p">,</span> <span class="n">toItem</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">attribute</span><span class="p">:</span> <span class="p">.</span><span class="n">notAnAttribute</span><span class="p">,</span> <span class="n">multiplier</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">constant</span><span class="p">:</span> <span class="mi">20</span><span class="p">))</span>
<span class="n">adChoicesView</span><span class="p">.</span><span class="n">updateFrameFromSuperview</span><span class="p">()</span>
<span class="c1">// 設定整個廣告的 container view 能夠被點擊。</span>
<span class="n">nativeAd</span><span class="p">.</span><span class="n">registerView</span><span class="p">(</span><span class="n">forInteraction</span><span class="p">:</span> <span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">,</span> <span class="n">with</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">isHidden</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
其中 adChoicesView 必需使用程式建立,然後用程式加上 Constraints 才行
不能在 storyboard 中建立,不然使用 Constraints 固定在右上角後會無法顯示
執行結果
<div class="img" data-ori_w="526" data-ori_h="613" style="width:263px;height:307px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/j1pZbNs.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 FBMediaView</span>
FBMediaView 可顯示大型圖片、可水平捲動的多張圖片、或是影片
要使用 FBMediaView 的話,先在 storyboard 的 prototype cell 拉一個比較大的版位
像這樣:
<div class="img" data-ori_w="1459" data-ori_h="523" style="width:730px;height:262px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/m5cjv9p.png" alt="[圖]" /></div>
FBMediaView 也是要用程式產生才行
中間用 UIView 拉的一大塊 adCoverMediaView 是用產生定位的 frame
圖片的高度需要 200pt,超過的話上下會被裁掉,
或是之後再用程式將圖片縮小
修改之前加上的自定函數 getFBAdsCell()
將 if let adCoverMediaView = cell.adCoverMediaView { … }
這段的內容改為
<div class="highlight"><span></span> <span class="c1">// 載入媒體圖片 (如果有的話)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">adCoverMediaView</span> <span class="p">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">adCoverMediaView</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">coverMediaView</span> <span class="p">=</span> <span class="n">FBMediaView</span><span class="p">(</span><span class="n">frame</span><span class="p">:</span> <span class="n">adCoverMediaView</span><span class="p">.</span><span class="n">frame</span><span class="p">)</span>
<span class="n">coverMediaView</span><span class="p">.</span><span class="n">nativeAd</span> <span class="p">=</span> <span class="n">nativeAd</span>
<span class="n">coverMediaView</span><span class="p">.</span><span class="n">clipsToBounds</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">coverMediaView</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">cell</span><span class="p">.</span><span class="n">adContainerView</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">coverMediaView</span><span class="p">)</span>
<span class="p">}</span>
</div>
加上了一行 coverMediaView.delegate = self
為了要在載入 FBMediaView 後執行 delegate 函數
在 class 後面加上協定 FBMediaViewDelegate,例如
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">HotTextViewController</span><span class="p">:</span> <span class="bp">UITableViewController</span><span class="p">,</span> <span class="n">FBNativeAdDelegate</span><span class="p">,</span> <span class="n">FBMediaViewDelegate</span> <span class="p">{</span>
</div>
加上 delegate 函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - FBMediaViewDelegate</span>
<span class="kd">func</span> <span class="nf">mediaViewDidLoad</span><span class="p">(</span><span class="kc">_</span> <span class="n">mediaView</span><span class="p">:</span> <span class="n">FBMediaView</span><span class="p">)</span> <span class="p">{</span>
<span class="n">mediaView</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span>
<span class="p">}</span>
</div>
要在這邊將取得的 mediaView 設為跟 tableView 的寬度相同
沒辦法使用 Constraints 自動調整寬度
如果版面配置沒辦法使用到 200pt 高的話
也可以在這邊將高度縮小
執行結果
<div class="img" data-ori_w="525" data-ori_h="449" style="width:263px;height:225px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/vXIwj7w.png" alt="[圖]" /></div>
<div class="img" data-ori_w="526" data-ori_h="448" style="width:263px;height:224px"><img style="max-width:100%;" data-ratio="2" src="https://i.imgur.com/OF34GIN.png" alt="[圖]" /></div>
<span style="color:#00F000">更新廣告內容</span>
例如想在點擊重整頁面按鈕時,更換顯示的廣告內容
可以在執行重整的函數(例如 refresh())中加上
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">refresh</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="k">if</span> <span class="n">fbNativeAd</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="n">fbNativeAd</span><span class="p">.</span><span class="n">unregisterView</span><span class="p">()</span>
<span class="p">}</span>
<span class="n">loadFBNativeAd</span><span class="p">()</span>
<span class="p">}</span>
</div>
參考
AppCoda <a href="https://www.appcoda.com.tw/facebook-ads-integration/" target="_blank" rel="nofollow">如何在 iOS App 中整合 Facebook 廣告</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-09-12 04:34:29</span>
<span class="record">※ 編輯: Knuckles 時間: 2018-09-12 02:06:50</span></div></pre>
Knuckles
[Xcode][Swift3] Disp BBS iOS App 2.0 開放原始碼
http://disp.cc/b/11-a9gj
2017-07-19T19:40:38+08:00
2017-07-19T19:41:33+08:00
Disp BBS iOS App 目前完成的程式碼放在 GitHub
https://github.com/KnucklesHuang/DispBBS-Swift/tree/ver2.0
有興趣可以抓來看看
程式執行畫面
程式操作影片
主要功能為:
不需登入即可
.瀏覽本日熱門文章
.瀏覽各大看板的文章
.搜尋看板、瀏覽最近看過的看板
註冊 Disp BBS 的帳號登入後
.可以在看板發表文章
.編輯已發表的文章
.在看板發文回覆其他人的文章
.查看站內信箱,可寄信與回覆信件
.在站內信箱新增備忘錄
.編輯文章時可上傳圖片
App Store 連結:
https://itunes.apple.com/tw/app/disp-bbs/id939152921
另外可參考精簡版本的分支 (只有熱門文章功能)
https://github.com/KnucklesHuang/DispBBS-Swift/tree/HotTextBrowser
相關程式筆記可參考前面的文章
http://disp.cc/b/KnucklesNote?ft=swift3 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] Disp BBS iOS App 2.0 開放原始碼<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-07-19 Wed. 19:40:36</div><hr color="#008080" />───────────────────
Disp BBS iOS App 目前完成的程式碼放在 GitHub
<a href="https://github.com/KnucklesHuang/DispBBS-Swift/tree/ver2.0" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/DispBBS-Swift/tree/ver2.0</a>
有興趣可以抓來看看
程式執行畫面
<div class="img" data-ori_w="1657" data-ori_h="925" style="width:829px;height:463px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/opIi6sX.png" alt="[圖]" /></div>
程式操作影片
<div class="video" data-src="//www.youtube.com/embed/JK-1bB2sNOM?autohide=1&"><img style="max-width:100%;" width="640" height="480" src="https://img.youtube.com/vi/JK-1bB2sNOM/0.jpg" /><div class="play_btn"></div></div>
主要功能為:
不需登入即可
.瀏覽本日熱門文章
.瀏覽各大看板的文章
.搜尋看板、瀏覽最近看過的看板
註冊 Disp BBS 的帳號登入後
.可以在看板發表文章
.編輯已發表的文章
.在看板發文回覆其他人的文章
.查看站內信箱,可寄信與回覆信件
.在站內信箱新增備忘錄
.編輯文章時可上傳圖片
App Store 連結:
<a href="https://itunes.apple.com/tw/app/disp-bbs/id939152921" target="_blank" rel="nofollow">https://itunes.apple.com/tw/app/disp-bbs/id939152921</a>
另外可參考精簡版本的分支 (只有熱門文章功能)
<a href="https://github.com/KnucklesHuang/DispBBS-Swift/tree/HotTextBrowser" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/DispBBS-Swift/tree/HotTextBrowser</a>
相關程式筆記可參考前面的文章
<a href="http://disp.cc/b/KnucklesNote?ft=swift3" target="_blank" rel="nofollow">http://disp.cc/b/KnucklesNote?ft=swift3</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-07-19 19:40:36</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-07-19 19:41:31</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 Google 的 Firebase
http://disp.cc/b/11-a4pE
2017-06-01T02:26:35+08:00
2017-06-07T21:55:43+08:00
Firebase 是 Google 出的一套整合 APP 各種工具的雲端開發平台
包含了 Analytics、Admob、錯誤訊息分析、後端資料儲存等等
官網有詳細介紹
https://firebase.google.com/
或是看介紹影片 (可以點設定選簡中字幕)
Introducing Firebase - YouTube
Firebase has all the tools you need to build a successful app. It helps you reach new users, keep them engaged, scale up an infrastructure to meet that deman...
在 Firebase 新增專案
在首頁點 Get Started 進入控制台
點新增專案
輸入專案名稱、地區,點建立專案
點「將 Firebase 加入您的 iOS 應用程式]
輸入專案的 Bundle ID 與 Apple ID
Apple ID 就是 App Store 網址中 idXXXXXXXXX 的 XXXXXXXXX
例如 h ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Google 的 Firebase<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-06-01 Thu. 02:25:09</div><hr color="#008080" />───────────────────
Firebase 是 Google 出的一套整合 APP 各種工具的雲端開發平台
包含了 Analytics、Admob、錯誤訊息分析、後端資料儲存等等
官網有詳細介紹
<a href="https://firebase.google.com/" target="_blank" rel="nofollow">https://firebase.google.com/</a>
或是看介紹影片 (可以點設定選簡中字幕)
<div style="background-color:#222; margin:.25em; padding:.25em; text-align:left;"><div class="quote_in"><a href="http://www.youtube.com/watch?v=O17OWyx08Cg" target="_blank" rel="nofollow"><strong>Introducing Firebase - YouTube</strong></a>
Firebase has all the tools you need to build a successful app. It helps you reach new users, keep them engaged, scale up an infrastructure to meet that deman...</div></div>
<div class="video" data-src="//www.youtube.com/embed/O17OWyx08Cg?autohide=1&"><img style="max-width:100%;" width="640" height="480" src="https://img.youtube.com/vi/O17OWyx08Cg/0.jpg" /><div class="play_btn"></div></div>
<span style="color:#00F000">在 Firebase 新增專案</span>
在首頁點 Get Started 進入控制台
<div class="img" data-ori_w="1140" data-ori_h="663" style="width:570px;height:332px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tirjigB.png" alt="[圖]" /></div>
點新增專案
<div class="img" data-ori_w="902" data-ori_h="822" style="width:451px;height:411px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oLJ4lnf.png" alt="[圖]" /></div>
輸入專案名稱、地區,點建立專案
<div class="img" data-ori_w="672" data-ori_h="558" style="width:336px;height:279px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LBJiSPE.png" alt="[圖]" /></div>
點「將 Firebase 加入您的 iOS 應用程式]
<div class="img" data-ori_w="926" data-ori_h="410" style="width:463px;height:205px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/wYNejE5.png" alt="[圖]" /></div>
輸入專案的 Bundle ID 與 Apple ID
<div class="img" data-ori_w="1070" data-ori_h="853" style="width:535px;height:427px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LFGI4Wm.png" alt="[圖]" /></div>
Apple ID 就是 App Store 網址中 idXXXXXXXXX 的 XXXXXXXXX
例如 <a href="https://itunes.apple.com/us/app/disp-bbs/id939152921" target="_blank" rel="nofollow">https://itunes.apple.com/us/app/disp-bbs/id939152921</a>
的 Apple ID 為 939152921
下載 GoogleService-Info.plist 並加到 Xcode 專案中
<div class="img" data-ori_w="1066" data-ori_h="950" style="width:533px;height:475px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/0hHITvf.png" alt="[圖]" /></div>
使用 CocoaPods 安裝 Firebase/Core
<div class="img" data-ori_w="1072" data-ori_h="884" style="width:536px;height:442px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oqsSq33.png" alt="[圖]" /></div>
新版的 Firebase 已經 FIRApp.configure()
改為 FirebaseApp.configure()
在 appDelegate.swift 加入 Firebase 設定
<div class="img" data-ori_w="1074" data-ori_h="1012" style="width:537px;height:506px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Nh7Y1Cy.png" alt="[圖]" /></div>
安裝完成
<div class="img" data-ori_w="937" data-ori_h="769" style="width:469px;height:385px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kGgoOAf.png" alt="[圖]" /></div>
<span style="color:#00F000">設定 Firebase 的 Google Analytics </span>
參考 <a href="https://firebase.google.com/docs/analytics/ios/start" target="_blank" rel="nofollow">https://firebase.google.com/docs/analytics/ios/start</a>
安裝 pod 'Firebase/Core' 就有包含 Firebase 的 Analytics 了
如果同時有安裝 pod 'Google/Analytics'
執行時會出現錯誤訊息:"Tracking ID must not be nil or empty"
解決方法參考 <a href="https://stackoverflow.com/questions/37660953/app-delegate-crashes-because-of-firebase-initialization" target="_blank" rel="nofollow">StackOverflow</a>
可以在 GoogleService-Info.plist 加上 TRACKING_ID: UA-XXXXXXXX-X
就可以同時使用兩套 Analytics
或是移除 pod 'Google/Analytics' 與相關的程式碼
使用 Firebase 的 Analytics 就好
Firebase 的 Analytics 安裝好就會自動記錄
<a href="https://support.google.com/firebase/answer/6317485" target="_blank" rel="nofollow">自動收集的事件</a> 與 <a href="https://support.google.com/firebase/answer/6317486" target="_blank" rel="nofollow">自動收集的使用者資料</a>
不用加上額外的程式碼
執行 App,然後在 Firebase 控制台的 Analytics
點「STREAMVIEW」,然後點「時間軸詳細訊息」,可以看到目前使用人數
<div class="img" data-ori_w="1594" data-ori_h="765" style="width:797px;height:383px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/1up85QQ.png" alt="[圖]" /></div>
<span style="color:#00F000">開啟 AdSupport Framework</span>
要啟動 AdSupport Framework 才能收集使用者的「年齡」、「性別」及「興趣」
在專案設定的「General」最下面的「Linked Frameworks and Libraries」
點「✚」
<div class="img" data-ori_w="1053" data-ori_h="379" style="width:527px;height:190px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/cY0eIRk.png" alt="[圖]" /></div>
新增「AdSupport.framework」
<div class="img" data-ori_w="602" data-ori_h="670" style="width:301px;height:335px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/3gmI4xI.png" alt="[圖]" /></div>
之後發佈App時,要勾選有使用 IDFA
<div class="img" data-ori_w="1278" data-ori_h="812" style="width:639px;height:406px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/vyB9lDK.png" alt="[圖]" /></div>
<span style="color:#00F000">安裝 Firebase 的 AdMob</span>
參考官方文件 <a href="https://firebase.google.com/docs/admob/ios/quick-start" target="_blank" rel="nofollow">https://firebase.google.com/docs/admob/ios/quick-start</a>
在 Podfile 加上
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'Firebase/AdMob'</span>
</div>
如果本來有裝獨立版本的 AdMob
可以將 pod 'Google-Mobile-Ads-SDK' 這行移除
但其實寫 pod 'Firebase/AdMob' 也是會將 'Google-Mobile-Ads-SDK' 裝上去
使用終端機在專案目錄執行
$ pod install --repo-update
其他設定和獨立版本的 AdMob 相同
參考 <a href="http://disp.cc/b/11-a4jr" target="_blank" rel="nofollow">[Xcode][Swift3] 加入 AdMob 原生廣告 - KnucklesNote板 - Disp BBS</a>
<span style="color:#00F000">連結 Firebase 與 AdMob</span>
若登入 AdMob 的帳號與登入 Firebase 的帳號不相同
要先將 AdMob 的帳號加入 Firebase 的專案管理員中
在 Firebase 控制台,點 Overview 右邊的 Settings,
點 Managed in Google Cloud Console 下的「權限」
<div class="img" data-ori_w="817" data-ori_h="277" style="width:409px;height:139px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/pKTVXr1.png" alt="[圖]" /></div>
在 IAM 與管理,點「新增」
<div class="img" data-ori_w="1145" data-ori_h="416" style="width:573px;height:208px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/1mILffY.png" alt="[圖]" /></div>
輸入 AdMob 帳號的 E-mail,角色選擇「Project」/「擁有者」
<div class="img" data-ori_w="822" data-ori_h="412" style="width:411px;height:206px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Jx2xehw.png" alt="[圖]" /></div>
點新增後系統會寄一封確認信到 AdMob 帳號的 E-mail
點確認信中的連結後,就可以使用 AdMob 帳號登入相同的 Firebase 的專案了
接著在 AdMob 的「分析」頁
在應用程式右邊點「連結 Firebase」
<div class="img" data-ori_w="1316" data-ori_h="409" style="width:658px;height:205px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gMxhY39.png" alt="[圖]" /></div>
若之前權限設定成功,這邊就可以連結至現有的 Firebase 專案
<div class="img" data-ori_w="902" data-ori_h="586" style="width:451px;height:293px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/JafaVs8.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-06-01 02:25:09</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-06-07 21:54:11</span></div></pre>
Knuckles
[Xcode][Swift3] 加入 AdMob 原生廣告
http://disp.cc/b/11-a4jr
2017-05-31T03:11:58+08:00
2017-06-06T01:45:25+08:00
在 http://www.google.com.tw/admob/ 登入 AdMob
在「營利」頁,新增一個應用程式
已新增過應用程式的話,直接點選營利頁下的應用程式
新增廣告單元,類型選擇原生廣告
點開始匯入後,廣告大小選擇小的
選擇範本,例如選第一個
修改顏色配置
點「驗證樣式」,確認沒問題後儲存
取得「應用程式ID」與「廣告單元 ID」
安裝 AdMob SDK
參考官方文件 https://developers.google.com/admob/ios/quick-start
使用 CocoaPods 安裝 AdMob SDK
CocoaPods 的安裝方法可參考這篇
[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS
在 Podfile 新增這行
pod 'Google-Mobile-Ads-SDK'
然後使用終端機在專案目錄執行 pod install
目前安裝的版本為 7.20.0
在 Xcode 設定廣告
修改 AppDelegate.swift
前面加上
import Goo ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加入 Admob 原生廣告<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-31 Wed. 03:12:11</div><hr color="#008080" />───────────────────
在 <a href="http://www.google.com.tw/admob/" target="_blank" rel="nofollow">http://www.google.com.tw/admob/</a> 登入 AdMob
<div class="img" data-ori_w="1231" data-ori_h="427" style="width:616px;height:214px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/jj4we7q.png" alt="[圖]" /></div>
在「營利」頁,新增一個應用程式
<div class="img" data-ori_w="723" data-ori_h="189" style="width:362px;height:95px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/vXCzpnl.png" alt="[圖]" /></div>
<div class="img" data-ori_w="1068" data-ori_h="470" style="width:534px;height:235px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/90zJ9pf.png" alt="[圖]" /></div>
已新增過應用程式的話,直接點選營利頁下的應用程式
<div class="img" data-ori_w="416" data-ori_h="525" style="width:208px;height:263px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/A76BH6z.png" alt="[圖]" /></div>
新增廣告單元,類型選擇原生廣告
<div class="img" data-ori_w="1922" data-ori_h="1363" style="width:961px;height:682px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/q5FE6bP.png" alt="[圖]" /></div>
點開始匯入後,廣告大小選擇小的
<div class="img" data-ori_w="1259" data-ori_h="472" style="width:630px;height:236px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/cKO563h.png" alt="[圖]" /></div>
選擇範本,例如選第一個
<div class="img" data-ori_w="1786" data-ori_h="252" style="width:893px;height:126px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/CoUJJBA.png" alt="[圖]" /></div>
修改顏色配置
<div class="img" data-ori_w="1885" data-ori_h="998" style="width:943px;height:499px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/F5NMhDu.png" alt="[圖]" /></div>
點「驗證樣式」,確認沒問題後儲存
<div class="img" data-ori_w="673" data-ori_h="88" style="width:337px;height:44px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/HQuUwmy.png" alt="[圖]" /></div>
取得「應用程式ID」與「廣告單元 ID」
<div class="img" data-ori_w="1063" data-ori_h="973" style="width:532px;height:487px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ORwXN1c.png" alt="[圖]" /></div>
<span style="color:#00F000">安裝 AdMob SDK</span>
參考官方文件 <a href="https://developers.google.com/admob/ios/quick-start" target="_blank" rel="nofollow">https://developers.google.com/admob/ios/quick-start</a>
使用 CocoaPods 安裝 AdMob SDK
CocoaPods 的安裝方法可參考這篇
<a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS</a>
在 Podfile 新增這行
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'Google-Mobile-Ads-SDK'</span>
</div>
然後使用終端機在專案目錄執行 pod install
目前安裝的版本為 7.20.0
<span style="color:#00F000">在 Xcode 設定廣告</span>
修改 AppDelegate.swift
前面加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">GoogleMobileAds</span>
</div>
在成員函數 func application(_:didFinishLaunchingWithOptions) 裡面加上
<div class="highlight"><span></span> <span class="c1">// Initialize the Google Mobile Ads SDK.</span>
<span class="c1">// Sample AdMob app ID: ca-app-pub-3940256099942544~1458002511</span>
<span class="n">GADMobileAds</span><span class="p">.</span><span class="n">configure</span><span class="p">(</span><span class="n">withApplicationID</span><span class="p">:</span> <span class="s">"YOUR_ADMOB_APP_ID"</span><span class="p">)</span>
</div>
注意這邊是加上應用程式 ID
可以在這邊取得
<div class="img" data-ori_w="1135" data-ori_h="514" style="width:568px;height:257px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/NMsRRod.png" alt="[圖]" /></div>
<span style="color:#00F000">設定 Native 廣告</span>
參考官方文件 <a href="https://developers.google.com/admob/ios/native-express" target="_blank" rel="nofollow">https://developers.google.com/admob/ios/native-express</a>
例如要將這篇教學
<a href="http://disp.cc/b/11-9VPx" target="_blank" rel="nofollow">[Xcode][Swift3] 新手教學 使用 Swift3 製作熱門文章瀏覽器 App - KnucklesNote板 - Disp BBS</a>
產生的熱門文章列表頁,在第六列插入一個原生廣告
新增一個類別檔 NativeAdsCell.swift
subclass of: UITableViewCell
在 storyboard 中,Table View 的設定增加一個 Prototype Cells
新的 Cell 設定 Identifier 為「NativeAdsCell」
以及設定自訂類別為「NativeAdsCell」
<div class="img" data-ori_w="1319" data-ori_h="373" style="width:660px;height:187px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/CFxE2sr.png" alt="[圖]" /></div>
在 NativeAdsCell 裡拉一個 View,設定四個方向的 Constraint 為 0
設定自訂類別為「GADNativeExpressAdView」
<div class="img" data-ori_w="1312" data-ori_h="383" style="width:656px;height:192px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/KVdlNlg.png" alt="[圖]" /></div>
(不能將 Cell 裡的 Content View 設為 GADNativeExpressAdView,會抓不到)
使用 Assistant Editor 拉一個 @IBOutlet 到 NativeAdsCell.swift
<div class="highlight"><span></span> <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">nativeExpressAdView</span><span class="p">:</span> <span class="n">GADNativeExpressAdView</span><span class="p">!</span>
</div>
在 NativeAdsCell.swift 前面加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">GoogleMobileAds</span>
</div>
修改 HotTextViewController.swift
前面先加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">GoogleMobileAds</span>
</div>
修改成員函數 tableView(_:numberOfRowsInSection:) 的內容為
<div class="highlight"><span></span> <span class="k">guard</span> <span class="kd">let</span> <span class="nv">num</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?.</span><span class="bp">count</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">num</span> <span class="o">>=</span> <span class="mi">5</span> <span class="p">{</span> <span class="c1">// Add Native Ads</span>
<span class="k">return</span> <span class="n">num</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">num</span>
<span class="p">}</span>
</div>
當熱門文章有五筆以上時,Table View 的列數加一
修改成員函數 tableView(_:cellForRowAt:) 的內容
在前面加上
<div class="highlight"><span></span> <span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="p">==</span> <span class="mi">5</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"NativeAdsCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">NativeAdsCell</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">nativeExpressAdView</span> <span class="p">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">nativeExpressAdView</span> <span class="p">{</span>
<span class="n">nativeExpressAdView</span><span class="p">.</span><span class="n">adUnitID</span> <span class="p">=</span> <span class="s">"YOUR_ADMOB_AD_UNIT_ID"</span>
<span class="n">nativeExpressAdView</span><span class="p">.</span><span class="n">rootViewController</span> <span class="p">=</span> <span class="kc">self</span>
<span class="kd">let</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">GADRequest</span><span class="p">()</span>
<span class="n">request</span><span class="p">.</span><span class="n">testDevices</span> <span class="p">=</span> <span class="p">[</span><span class="n">kGADSimulatorID</span><span class="p">]</span>
<span class="n">nativeExpressAdView</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
在第六列的時候,將 cell 設定為原生廣告
原本第六列後的內容要往下移一列,
將原本的 let hotText = self.hotTextArray?[indexPath.row] 改為
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">index</span> <span class="p">=</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="o">></span> <span class="mi">5</span> <span class="p">{</span>
<span class="n">index</span> <span class="p">=</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="o">-</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nv">hotText</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?[</span><span class="n">index</span><span class="p">]</span>
</div>
第六列後取資料的 index = indexPath.row - 1
修改點擊列表會執行的成員函數 prepare(for:sender:)
將
guard let textViewController = segue.destination as? TextViewController,
let row = self.tableView.indexPathForSelectedRow?.row,
let hotText = self.hotTextArray?[row] as? [String: Any]
else { return }
改為
<div class="highlight"><span></span> <span class="k">guard</span> <span class="kd">let</span> <span class="nv">textViewController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">?</span> <span class="n">TextViewController</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">row</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">indexPathForSelectedRow</span><span class="p">?.</span><span class="n">row</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="kd">var</span> <span class="nv">index</span> <span class="p">=</span> <span class="n">row</span>
<span class="k">if</span> <span class="n">row</span> <span class="p">==</span> <span class="mi">5</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="n">row</span> <span class="o">></span> <span class="mi">5</span> <span class="p">{</span> <span class="n">index</span> <span class="p">=</span> <span class="n">row</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">}</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">hotText</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?[</span><span class="n">index</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</div>
點擊第5列時跳出,點擊第6列以上時,取資料的 index = row - 1
為了避免違反 AdMob 使用規定,
在模擬器測試的時候要加上 request.testDevices = [kGADSimulatorID]
以免跑出會計費的廣告 (只顯示沒點擊也不行)
在實際裝置測試時,下面的 Console 視窗會出現這樣的訊息
<Google> To get test ads on this device, call: request.testDevices = @[@"XXXXXXXXXXXXXXXXXXXXXX"];
再將 Device ID 加進 testDevices 陣列中
例如: request.testDevices = [kGADSimulatorID, "XXXXXXXXXXXXXXXXXXXXXX"]
發佈 App 前再將 request.testDevices = ... 這行拿掉
執行結果
<div class="img" data-ori_w="524" data-ori_h="932" style="width:262px;height:466px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/pJ3QFlV.png" alt="[圖]" /></div>
在 Admob 中新增廣告單元後,要一段時間後才會顯示
所以如果執行結果廣告欄位是空的,可以過一段時間後再試試看
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-31 03:12:11</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-06-06 01:45:24</span></div></pre>
Knuckles
[Xcode][Swift3] 加入 MoPub 廣告中介服務 (失敗)
http://disp.cc/b/11-a3FZ
2017-05-25T02:28:18+08:00
2017-09-12T02:02:03+08:00
====================================
最後沒辦法安裝成功,以下僅供參考
====================================
使用 MoPub 在 ListView 列表中加上原生廣告(Native Ads)
在 MoPub 網站新增廣告單元
在 MoPub 網站 https://www.mopub.com/
註冊一個帳號後登入
新增一個 App
新增一個 Native 的廣告單元
這邊我們設定廣告顯示位置為5 (從0開始算,也就是在第6列)
按儲存後,取得 Ad Unit ID
在 Xcode 安裝 MoPub SDK
參考 https://github.com/mopub/mopub-ios-sdk/wiki
裡的 Getting Started
使用套件管理程式 CocoaPods 來安裝
CocoaPods 的安裝方法可參考這篇
[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS
在 Podfile 新增這行
pod 'mopub ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加入 MoPub 廣告中介服務<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-25 Thu. 02:26:59</div><hr color="#008080" />───────────────────
====================================
最後沒辦法安裝成功,以下僅供參考
====================================
<div class="img" data-ori_w="408" data-ori_h="208" style="width:204px;height:104px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/n0R2U5V.png" alt="[圖]" /></div>
使用 MoPub 在 ListView 列表中加上原生廣告(Native Ads)
<span style="color:#00F000">在 MoPub 網站新增廣告單元</span>
在 MoPub 網站 <a href="https://www.mopub.com/" target="_blank" rel="nofollow">https://www.mopub.com/</a>
註冊一個帳號後登入
新增一個 App
<div class="img" data-ori_w="1457" data-ori_h="118" style="width:729px;height:59px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/AjuBANw.png" alt="[圖]" /></div>
<div class="img" data-ori_w="1451" data-ori_h="382" style="width:726px;height:191px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Gkkv6ze.png" alt="[圖]" /></div>
新增一個 Native 的廣告單元
<div class="img" data-ori_w="1471" data-ori_h="827" style="width:736px;height:414px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/MipQCov.png" alt="[圖]" /></div>
這邊我們設定廣告顯示位置為5 (從0開始算,也就是在第6列)
按儲存後,取得 Ad Unit ID
<div class="img" data-ori_w="1455" data-ori_h="353" style="width:728px;height:177px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rhY6Qa3.png" alt="[圖]" /></div>
<span style="color:#00F000">在 Xcode 安裝 MoPub SDK</span>
參考 <a href="https://github.com/mopub/mopub-ios-sdk/wiki" target="_blank" rel="nofollow">https://github.com/mopub/mopub-ios-sdk/wiki</a>
裡的 <a href="https://github.com/mopub/mopub-ios-sdk/wiki/Getting-Started" target="_blank" rel="nofollow">Getting Started</a>
使用套件管理程式 CocoaPods 來安裝
CocoaPods 的安裝方法可參考這篇
<a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS</a>
在 Podfile 新增這行
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'mopub-ios-sdk'</span>
</div>
然後使用終端機在專案目錄執行 pod install
目前安裝的版本為 4.14.0
<span style="color:#00F000">新增 BridgingHeader</span>
因為 mopub 是用 Objective-C 寫的,要在 Swift 中使用的話,要加上 BridgingHeader
之前沒加過的話,點 command+n 新增檔案,選「Header File」
<div class="img" data-ori_w="1126" data-ori_h="802" style="width:563px;height:401px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ofqePJK.png" alt="[圖]" /></div>
檔案名稱輸入「BridgingHeader」
在專案設定的「Build Settings」,選「All」,
在右邊的搜尋框輸入「bridging」,
在下面出現的 Objective-C Bridging Header 輸入「$(PROJECT_NAME)/BridgingHeader.h」
<div class="img" data-ori_w="1459" data-ori_h="306" style="width:730px;height:153px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6zazBme.png" alt="[圖]" /></div>
在搜尋框輸入「search path」,
在下面出現的 User Header Search Paths 右邊的值點兩下
在跳出的輸入框新增「Pods」,右邊選「recursive」
<div class="img" data-ori_w="1703" data-ori_h="921" style="width:852px;height:461px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/BzcxB60.png" alt="[圖]" /></div>
讓 Xcode 會去 Pods 目錄中尋找 Header 檔案
修改 BridgingHeader.h
在 #define ... 與 #endif 之間加上
<div class="highlight"><span></span><span class="cp">#import "MoPubSDK/MoPub-Bridging-Header.h"</span>
</div>
<span style="color:#00F000">加上 MoPub Native Ads</span>
新增一個 NativeAdView.swift 檔
將內容改為
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">UIKit</span>
<span class="kd">class</span> <span class="nc">NativeAdView</span><span class="p">:</span> <span class="bp">UIView</span><span class="p">,</span> <span class="n">MPNativeAdRendering</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nv">titleLabel</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kd">var</span> <span class="nv">mainTextLabel</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kd">var</span> <span class="nv">callToActionLabel</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kd">var</span> <span class="nv">iconImageView</span><span class="p">:</span> <span class="bp">UIImageView</span><span class="p">!</span>
<span class="kd">var</span> <span class="nv">mainImageView</span><span class="p">:</span> <span class="bp">UIImageView</span><span class="p">!</span>
<span class="kd">private</span> <span class="kd">var</span> <span class="nv">privacyInformationIconImageView</span><span class="p">:</span> <span class="bp">UIImageView</span><span class="p">!</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">layoutSubviews</span><span class="p">()</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">layoutSubviews</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeMainTextLabel</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UILabel</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">mainTextLabel</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeTitleTextLabel</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UILabel</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">titleLabel</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeCallToActionTextLabel</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UILabel</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">callToActionLabel</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeIconImageView</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UIImageView</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">iconImageView</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativeMainImageView</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UIImageView</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">mainImageView</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">nativePrivacyInformationIconImageView</span><span class="p">()</span> <span class="p">-></span> <span class="bp">UIImageView</span><span class="p">!</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">self</span><span class="p">.</span><span class="n">privacyInformationIconImageView</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
修改要加上 NativeAds 的 TableViewController
例如 HotTextViewController.swift
在 class HotTextViewController: 後面加上 MPTableViewAdPlacerDelegate
加上成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">placer</span><span class="p">:</span> <span class="n">MPTableViewAdPlacer</span><span class="p">!</span>
</div>
加上成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">initAdPlacer</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">settings</span><span class="p">:</span> <span class="n">MPStaticNativeAdRendererSettings</span> <span class="p">=</span> <span class="n">MPStaticNativeAdRendererSettings</span><span class="p">()</span>
<span class="n">settings</span><span class="p">.</span><span class="n">renderingViewClass</span> <span class="p">=</span> <span class="n">NativeAdView</span><span class="p">.</span><span class="kc">self</span>
<span class="n">settings</span><span class="p">.</span><span class="n">viewSizeHandler</span> <span class="p">=</span> <span class="p">{</span> <span class="p">(</span><span class="n">maxWidth</span><span class="p">:</span> <span class="n">CGFloat</span><span class="p">)</span> <span class="p">-></span> <span class="n">CGSize</span> <span class="k">in</span>
<span class="k">return</span> <span class="n">CGSize</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="n">maxWidth</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mf">312.0</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nv">config</span><span class="p">:</span> <span class="n">MPNativeAdRendererConfiguration</span> <span class="p">=</span> <span class="n">MPStaticNativeAdRenderer</span><span class="p">.</span><span class="n">rendererConfiguration</span><span class="p">(</span><span class="n">with</span><span class="p">:</span> <span class="n">settings</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">placer</span> <span class="p">=</span> <span class="n">MPTableViewAdPlacer</span><span class="p">(</span><span class="n">tableView</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">,</span> <span class="n">viewController</span><span class="p">:</span> <span class="kc">self</span><span class="p">,</span> <span class="n">rendererConfigurations</span><span class="p">:</span> <span class="p">[</span><span class="n">config</span><span class="p">])</span>
<span class="kc">self</span><span class="p">.</span><span class="n">placer</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="kd">let</span> <span class="nv">targeting</span><span class="p">:</span> <span class="n">MPNativeAdRequestTargeting</span> <span class="p">=</span> <span class="n">MPNativeAdRequestTargeting</span><span class="p">()</span>
<span class="n">targeting</span><span class="p">.</span><span class="n">desiredAssets</span> <span class="p">=</span> <span class="n">Set</span><span class="p">([</span><span class="n">kAdIconImageKey</span><span class="p">,</span> <span class="n">kAdCTATextKey</span><span class="p">,</span> <span class="n">kAdTextKey</span><span class="p">,</span> <span class="n">kAdTitleKey</span><span class="p">])</span>
<span class="kc">self</span><span class="p">.</span><span class="n">placer</span><span class="p">.</span><span class="n">loadAds</span><span class="p">(</span><span class="n">forAdUnitID</span><span class="p">:</span> <span class="s">"your_ad_unit_id"</span><span class="p">,</span> <span class="n">targeting</span><span class="p">:</span> <span class="n">targeting</span><span class="p">)</span>
<span class="p">}</span>
</div>
在 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="n">initAdPlacer</span><span class="p">()</span>
</div>
(未完待續)
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-25 02:26:59</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-09-12 02:01:18</span></div></pre>
Knuckles
[Xcode][Swift3] 加上 Google Analytics 追蹤 APP 使用情形
http://disp.cc/b/11-a3pr
2017-05-23T02:21:41+08:00
2017-06-03T14:42:28+08:00
=== 後記 ===
參考 [Xcode][Swift3] 使用 Google 的 Firebase - KnucklesNote板 - Disp BBS
後來發現使用 Firebase 的 Analytics 就可以了,不用裝這個
GA 現在不能新增 APP 的追踨編號了,要先在 Firebase 新增應用程式後
在 GA 裡連結 Firebase 的應用程式
============
在 Google Analytics 新增追蹤編號
登入 Google Analytics 後,在「管理員」選擇一個 GA 帳戶
在這個 GA 帳戶裡新建一個資源
選擇資源類型為「行動應用程式」,輸入APP的名稱,選擇產業類別
要啟用 IDFA (廣告客戶識別碼) 功能的話,在資源設定打開「啟用客層和興趣報表」
記下自己的追蹤編號 UA-XXXXXXXX
安裝 Google Analytics iOS SDK
參考 Google 的說明文件:
https://developers.google.com/analytics/devguides/collection/ios/v3/?ver=swift
使 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加上 Google Analytics 追蹤 APP 使用情形<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-23 Tue. 02:21:52</div><hr color="#008080" />───────────────────
=== 後記 ===
參考 <a href="http://disp.cc/b/11-a4pE" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Google 的 Firebase - KnucklesNote板 - Disp BBS</a>
後來發現使用 Firebase 的 Analytics 就可以了,不用裝這個
GA 現在不能新增 APP 的追踨編號了,要先在 Firebase 新增應用程式後
在 GA 裡連結 Firebase 的應用程式
============
<span style="color:#00F000">在 Google Analytics 新增追蹤編號</span>
登入 Google Analytics 後,在「管理員」選擇一個 GA 帳戶
在這個 GA 帳戶裡新建一個資源
<div class="img" data-ori_w="750" data-ori_h="387" style="width:750px;height:387px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/wVqALIJ.png" alt="[圖]" /></div>
選擇資源類型為「行動應用程式」,輸入APP的名稱,選擇產業類別
<div class="img" data-ori_w="584" data-ori_h="585" style="width:584px;height:585px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/6WzAU8y.png" alt="[圖]" /></div>
要啟用 IDFA (廣告客戶識別碼) 功能的話,在資源設定打開「啟用客層和興趣報表」
<div class="img" data-ori_w="1024" data-ori_h="521" style="width:1024px;height:521px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/76BHq0C.png" alt="[圖]" /></div>
記下自己的追蹤編號 UA-XXXXXXXX
<div class="img" data-ori_w="861" data-ori_h="366" style="width:861px;height:366px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/0CPAhLE.png" alt="[圖]" /></div>
<span style="color:#00F000">安裝 Google Analytics iOS SDK</span>
參考 Google 的說明文件:
<a href="https://developers.google.com/analytics/devguides/collection/ios/v3/?ver=swift" target="_blank" rel="nofollow">https://developers.google.com/analytics/devguides/collection/ios/v3/?ver=swift</a>
使用 CocoaPod 安裝 GoogleAnalytics SDK
CocoaPod 的使用方法可參考這篇
<a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS</a>
修改 Podfile,加上
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'Google/Analytics'</span>
</div>
開啟終端機,在專案目錄執行 pod install
目前安裝的版本為 GoogleAnalytics 3.17.0
<span style="color:#00F000">新增 BridgingHeader</span>
因為 Analytics 是使用 Objective-C 寫的,
要在 Swift 專案中使用 Objective-C 的話,要先加上 BridgingHeader
點 command+n 新增檔案,選「Header File」
<div class="img" data-ori_w="1126" data-ori_h="802" style="width:563px;height:401px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ofqePJK.png" alt="[圖]" /></div>
檔案名稱輸入「BridgingHeader」
在專案設定的「Build Settings」,選「All」,
在右邊的搜尋框輸入「bridging」,
在下面出現的 Objective-C Bridging Header 輸入「$(PROJECT_NAME)/BridgingHeader.h」
<div class="img" data-ori_w="1459" data-ori_h="306" style="width:730px;height:153px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6zazBme.png" alt="[圖]" /></div>
<span style="color:#00F000">設定 GoogleAnalytics</span>
修改 BridgingHeader.h
在 #define ... 與 #endif 之間加上
<div class="highlight"><span></span><span class="cp">#import <Google/Analytics.h></span>
</div>
修改 AppDelegate.swift
在成員函數 application(_:didFinishLaunchingWithOption:) 中加上
<div class="highlight"><span></span><span class="k">guard</span> <span class="kd">let</span> <span class="nv">gai</span> <span class="p">=</span> <span class="n">GAI</span><span class="p">.</span><span class="n">sharedInstance</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
<span class="bp">assert</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="s">"Google Analytics not configured correctly"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">gai</span><span class="p">.</span><span class="n">tracker</span><span class="p">(</span><span class="n">withTrackingId</span><span class="p">:</span> <span class="s">"YOUR_TRACKING_ID"</span><span class="p">)</span>
<span class="c1">// Optional: automatically report uncaught exceptions.</span>
<span class="n">gai</span><span class="p">.</span><span class="n">trackUncaughtExceptions</span> <span class="p">=</span> <span class="kc">true</span>
<span class="c1">// Optional: set Logger to VERBOSE for debug information.</span>
<span class="c1">// Remove before app release.</span>
<span class="n">gai</span><span class="p">.</span><span class="n">logger</span><span class="p">.</span><span class="n">logLevel</span> <span class="p">=</span> <span class="p">.</span><span class="n">verbose</span><span class="p">;</span>
</div>
將 "YOUR_TRACKING_ID" 改成自己的追蹤編號 "UA-XXXXXXXX"
<span style="color:#00F000">加上頁面追蹤</span>
在想要追蹤的頁面加上
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewWillAppear</span><span class="p">(</span><span class="kc">_</span> <span class="n">animated</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewDidAppear</span><span class="p">(</span><span class="n">animated</span><span class="p">)</span>
<span class="c1">// Google Analytics</span>
<span class="kd">let</span> <span class="nv">screenName</span> <span class="p">=</span> <span class="s">"MyPage"</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">tracker</span> <span class="p">=</span> <span class="n">GAI</span><span class="p">.</span><span class="n">sharedInstance</span><span class="p">().</span><span class="n">defaultTracker</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="n">tracker</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">kGAIScreenName</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">screenName</span><span class="p">)</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">builder</span> <span class="p">=</span> <span class="n">GAIDictionaryBuilder</span><span class="p">.</span><span class="n">createScreenView</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="n">tracker</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">build</span><span class="p">()</span> <span class="k">as</span> <span class="p">[</span><span class="bp">NSObject</span> <span class="p">:</span> <span class="nb">AnyObject</span><span class="p">])</span>
</div>
執行看看,切換到要追蹤頁面後,
過一會在 Google Analytics 的即時就會看到有一個使用者了
<div class="img" data-ori_w="1425" data-ori_h="676" style="width:713px;height:338px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/dfy8bLW.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-23 02:21:52</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-06-03 14:42:28</span>
文章作者可對每則推文做回應...
文章作者可對每則推文做回應...
文章作者可對每則推文做回應...</div></pre>
Knuckles
[Xcode][Swift3] 使用 ActivityViewController 分享至其他程式
http://disp.cc/b/11-a1Nb
2017-05-09T20:59:36+08:00
2017-05-09T21:20:27+08:00
使用 ActivityViewController 開啟 iOS 的分享功能
在 Navigation Bar 加上一個 Bar Button Item
在屬性檢視器將 System Item 設為 Action
使用 Assisant Editor 加上 @IBAction
名稱輸入「share」
修改 @IBAction 的內容為
@IBAction func share(_ sender: Any) {
guard textTitle != nil, boardName != nil, boardId != nil, textId != nil
else { return }
let shareTitle = "\(textTitle!) - \(boardName!)板 - DispBBS"
let urlString = "https://disp.cc/b/\(boardId!)-\(textId!)"
let shareUrl = NSU ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 ActivityViewController 分享至其他程式<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-09 Tue. 20:59:43</div><hr color="#008080" />───────────────────
使用 ActivityViewController 開啟 iOS 的分享功能
<div class="img" data-ori_w="461" data-ori_h="819" style="width:231px;height:410px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/S3OUGTm.png" alt="[圖]" /></div>
在 Navigation Bar 加上一個 Bar Button Item
在屬性檢視器將 System Item 設為 Action
使用 Assisant Editor 加上 @IBAction
名稱輸入「share」
修改 @IBAction 的內容為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">share</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="n">textTitle</span> <span class="o">!=</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">boardName</span> <span class="o">!=</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">boardId</span> <span class="o">!=</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">textId</span> <span class="o">!=</span> <span class="kc">nil</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="kd">let</span> <span class="nv">shareTitle</span> <span class="p">=</span> <span class="s">"</span><span class="si">\(</span><span class="n">textTitle</span><span class="p">!</span><span class="si">)</span><span class="s"> - </span><span class="si">\(</span><span class="n">boardName</span><span class="p">!</span><span class="si">)</span><span class="s">板 - DispBBS"</span>
<span class="kd">let</span> <span class="nv">urlString</span> <span class="p">=</span> <span class="s">"<a href="https://disp.cc/b/" target="_blank" rel="nofollow">https://disp.cc/b/</a></span><span class="si">\(</span><span class="n">boardId</span><span class="p">!</span><span class="si">)</span><span class="s">-</span><span class="si">\(</span><span class="n">textId</span><span class="p">!</span><span class="si">)</span><span class="s">"</span>
<span class="kd">let</span> <span class="nv">shareUrl</span> <span class="p">=</span> <span class="bp">NSURL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span><span class="o">!</span>
<span class="kd">let</span> <span class="nv">activityViewController</span> <span class="p">:</span> <span class="bp">UIActivityViewController</span> <span class="p">=</span> <span class="bp">UIActivityViewController</span><span class="p">(</span>
<span class="n">activityItems</span><span class="p">:</span> <span class="p">[</span><span class="n">shareTitle</span><span class="p">,</span> <span class="n">shareUrl</span><span class="p">],</span> <span class="n">applicationActivities</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="c1">// 要加這行,不然在iPad會閃退</span>
<span class="n">activityViewController</span><span class="p">.</span><span class="n">popoverPresentationController</span><span class="p">?.</span><span class="n">barButtonItem</span> <span class="p">=</span> <span class="n">sender</span> <span class="k">as</span><span class="p">?</span> <span class="bp">UIBarButtonItem</span>
<span class="c1">// 如果有不想列出來的分享選項,加上下面這行可排除</span>
<span class="c1">//activityViewController.excludedActivityTypes = [ .postToWeibo, .print, .assignToContact, .saveToCameraRoll, .addToReadingList, .postToFlickr, .postToVimeo, .postToTencentWeibo ]</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">activityViewController</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
在 iPad 會使用 popoverPresentationController 來顯示
<div class="img" data-ori_w="920" data-ori_h="421" style="width:460px;height:211px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/cfO11YD.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-09 20:59:43</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-09 21:20:34</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 Imgur API 加上上傳圖片功能
http://disp.cc/b/11-a1zb
2017-05-07T23:48:50+08:00
2017-07-19T18:09:33+08:00
在編輯器加上一個加入圖片的按鈕
使用自訂圖示的方法參考 [Xcode][Swift3] 加入Google提供的圖示 Material icons - KnucklesNote板 - Disp BBS
使用 Assistant Editor 加入點擊按鈕的 @IBAction
名稱輸入「addPhoto」
修改編輯器的類別程式檔 EditorViewController.swift
類別加上繼承 UIImagePickerControllerDelegate, UINavigationControllerDelegate
class EditorViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
新增成員變數
let imagePicker = UIImagePickerController()
建立一個 UIImagePickerController 在成員變數
避免每次加入圖片時都要再建立一次
在 viewDidLoad() 裡加 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加上上傳圖片功能<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-07 Sun. 23:48:57</div><hr color="#008080" />───────────────────
在編輯器加上一個加入圖片的按鈕
<div class="img" data-ori_w="951" data-ori_h="412" style="width:476px;height:206px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/T4iUn8z.png" alt="[圖]" /></div>
使用自訂圖示的方法參考 <a href="http://disp.cc/b/11-a0JJ" target="_blank" rel="nofollow">[Xcode][Swift3] 加入Google提供的圖示 Material icons - KnucklesNote板 - Disp BBS</a>
使用 Assistant Editor 加入點擊按鈕的 @IBAction
名稱輸入「addPhoto」
修改編輯器的類別程式檔 EditorViewController.swift
類別加上繼承 UIImagePickerControllerDelegate, UINavigationControllerDelegate
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">EditorViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">,</span> <span class="bp">UIImagePickerControllerDelegate</span><span class="p">,</span> <span class="bp">UINavigationControllerDelegate</span> <span class="p">{</span>
</div>
新增成員變數
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">imagePicker</span> <span class="p">=</span> <span class="bp">UIImagePickerController</span><span class="p">()</span>
</div>
建立一個 UIImagePickerController 在成員變數
避免每次加入圖片時都要再建立一次
在 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="n">imagePicker</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
</div>
修改 addPhoto() 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">addPhoto</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">imagePicker</span><span class="p">.</span><span class="n">allowsEditing</span> <span class="p">=</span> <span class="kc">false</span>
<span class="n">imagePicker</span><span class="p">.</span><span class="n">sourceType</span> <span class="p">=</span> <span class="p">.</span><span class="n">photoLibrary</span>
<span class="n">present</span><span class="p">(</span><span class="n">imagePicker</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
imagePicker.allowsEditing = false
設定選擇圖片的頁面不能編輯
imagePicker.sourceType = .photoLibrary
設定選擇圖片時使用手機的相簿
加上兩個 delegate 函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - UIImagePickerControllerDelegate</span>
<span class="kd">func</span> <span class="nf">imagePickerController</span><span class="p">(</span><span class="kc">_</span> <span class="n">picker</span><span class="p">:</span> <span class="bp">UIImagePickerController</span><span class="p">,</span> <span class="n">didFinishPickingMediaWithInfo</span> <span class="n">info</span><span class="p">:</span> <span class="p">[</span><span class="nb">String</span> <span class="p">:</span> <span class="nb">Any</span><span class="p">])</span> <span class="p">{</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">pickedImage</span> <span class="p">=</span> <span class="n">info</span><span class="p">[</span><span class="n">UIImagePickerControllerOriginalImage</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="bp">UIImage</span> <span class="p">{</span>
<span class="c1">// 上傳圖片的程式 uploadImage() 寫在後面</span>
<span class="n">uploadImage</span><span class="p">(</span><span class="n">pickedImage</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dismiss</span><span class="p">(</span><span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">imagePickerControllerDidCancel</span><span class="p">(</span><span class="kc">_</span> <span class="n">picker</span><span class="p">:</span> <span class="bp">UIImagePickerController</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dismiss</span><span class="p">(</span><span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
第一個函數在取得選取的圖片後,將選取的圖片做處理
使用 dismiss 退回編輯器
第二個函數在選取圖片時點了 Cancel 後
使用 dismiss 退回編輯器
從 iOS 10 開始,要讀取手機相簿必需先設定隱私權限
否則一讀取就會閃退,並出現錯誤訊息:
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.
在專案設定的「Info」,點一下任意項目後面的✚
輸入「Privacy」,下面就會列出各種權限設定
選擇「Privacy - Photo Library Usage Description」
<div class="img" data-ori_w="1420" data-ori_h="303" style="width:710px;height:152px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Mcual3S.png" alt="[圖]" /></div>
後面的 Value 輸入需要權限的理由「加入圖片需要讀取相簿的權限」
<div class="img" data-ori_w="886" data-ori_h="145" style="width:443px;height:73px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/S5duRf9.png" alt="[圖]" /></div>
執行看看,第一次點了加入圖片的按鈕時,會出現要求權限
<div class="img" data-ori_w="525" data-ori_h="792" style="width:263px;height:396px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tPVrYbL.png" alt="[圖]" /></div>
點了 OK 後,就可以在相簿選取圖片了
<div class="img" data-ori_w="525" data-ori_h="413" style="width:263px;height:207px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/BZkq1sa.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Imgur API 上傳圖片</span>
先參考 <a href="http://disp.cc/b/11-8qWb" target="_blank" rel="nofollow">[PHP] 使用 Imgur API 上傳圖片 - KnucklesNote板 - Disp BBS</a>
取得 Imgur 的 Client ID 以及 Mashape Key
參考 <a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
安裝 Alamofire 以及 AlamofireImage
加上成員函數 uploadImage()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">uploadImage</span><span class="p">(</span><span class="n">image</span><span class="p">:</span> <span class="bp">UIImage</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">urlString</span> <span class="p">=</span> <span class="s">"https://imgur-apiv3.p.mashape.com/3/image/"</span>
<span class="c1">// 在下面兩行設定自己的 Client ID 以及 Mashape Key</span>
<span class="kd">let</span> <span class="nv">authorization</span> <span class="p">=</span> <span class="s">"Client-ID </span><span class="si">\(</span><span class="n">my_client_ID</span><span class="si">)</span><span class="s">"</span>
<span class="kd">let</span> <span class="nv">mashapeKey</span> <span class="p">=</span> <span class="s">"</span><span class="si">\(</span><span class="n">my_mashape_key</span><span class="si">)</span><span class="s">"</span>
<span class="kd">let</span> <span class="nv">width</span> <span class="p">=</span> <span class="n">image</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">*</span> <span class="n">image</span><span class="p">.</span><span class="n">scale</span>
<span class="kd">let</span> <span class="nv">height</span> <span class="p">=</span> <span class="n">image</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">*</span> <span class="n">image</span><span class="p">.</span><span class="n">scale</span>
<span class="c1">//print("width: \(width), height: \(height), scale: \(image.scale)")</span>
<span class="c1">// 圖片太大的話,將圖片縮小 </span>
<span class="kd">var</span> <span class="nv">scaledWidth</span> <span class="p">=</span> <span class="n">width</span><span class="p">,</span> <span class="n">scaledHeight</span> <span class="p">=</span> <span class="n">height</span>
<span class="k">if</span> <span class="n">scaledWidth</span> <span class="o">></span> <span class="mi">1200</span> <span class="p">{</span> <span class="c1">// 寬度大於1200的話,等比例縮小到寬度為1024</span>
<span class="n">scaledWidth</span> <span class="p">=</span> <span class="mf">1024.0</span>
<span class="n">scaledHeight</span> <span class="p">=</span> <span class="n">height</span> <span class="o">*</span> <span class="mf">1024.0</span> <span class="o">/</span> <span class="n">width</span>
<span class="p">}</span>
<span class="c1">// af_imageScaled 產生的寬高會乘以 deviceScale,所以設定的寬高要先除以 deviceScale</span>
<span class="kd">let</span> <span class="nv">deviceScale</span> <span class="p">=</span> <span class="bp">UIScreen</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">scale</span>
<span class="kd">let</span> <span class="nv">size</span> <span class="p">=</span> <span class="n">CGSize</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="n">scaledWidth</span><span class="o">/</span><span class="n">deviceScale</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="n">scaledHeight</span><span class="o">/</span><span class="n">deviceScale</span><span class="p">)</span>
<span class="c1">// 使用 AlamofireImage 提供的 af_imageScaled 來縮圖</span>
<span class="kd">let</span> <span class="nv">scaledImage</span> <span class="p">=</span> <span class="n">image</span><span class="p">.</span><span class="n">af_imageScaled</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">size</span><span class="p">)</span>
<span class="c1">// 將圖片轉為 base64 字串 </span>
<span class="kd">let</span> <span class="nv">imageData</span> <span class="p">=</span> <span class="n">UIImagePNGRepresentation</span><span class="p">(</span><span class="n">scaledImage</span><span class="p">)</span><span class="o">!</span>
<span class="kd">let</span> <span class="nv">imageBase64</span> <span class="p">=</span> <span class="n">imageData</span><span class="p">.</span><span class="n">base64EncodedString</span><span class="p">()</span>
<span class="kd">let</span> <span class="nv">headers</span><span class="p">:</span> <span class="n">HTTPHeaders</span> <span class="p">=</span> <span class="p">[</span><span class="s">"Authorization"</span><span class="p">:</span> <span class="n">authorization</span><span class="p">,</span> <span class="s">"X-Mashape-Key"</span><span class="p">:</span> <span class="n">mashapeKey</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">parameters</span><span class="p">:</span> <span class="n">Parameters</span> <span class="p">=</span> <span class="p">[</span><span class="s">"image"</span><span class="p">:</span> <span class="n">imageBase64</span><span class="p">]</span>
<span class="n">Alamofire</span><span class="p">.</span><span class="n">request</span><span class="p">(</span><span class="n">urlString</span><span class="p">,</span> <span class="n">method</span><span class="p">:</span> <span class="p">.</span><span class="n">post</span><span class="p">,</span> <span class="n">parameters</span><span class="p">:</span> <span class="n">parameters</span><span class="p">,</span> <span class="n">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">).</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="k">guard</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">errorMessage</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">?.</span><span class="n">localizedDescription</span>
<span class="kc">self</span><span class="p">.</span><span class="n">alert</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="n">errorMessage</span><span class="p">!)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">JSON</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">alert</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="s">"JSON formate error"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">success</span> <span class="p">=</span> <span class="n">JSON</span><span class="p">[</span><span class="s">"success"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">Bool</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">data</span> <span class="p">=</span> <span class="n">JSON</span><span class="p">[</span><span class="s">"data"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">alert</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="s">"JSON formate error"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="o">!</span><span class="n">success</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">message</span> <span class="p">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"error"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span> <span class="p">??</span> <span class="s">"error"</span>
<span class="kc">self</span><span class="p">.</span><span class="n">alert</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">link</span> <span class="p">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"link"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">width</span> <span class="p">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"width"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">Int</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">height</span> <span class="p">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"height"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">Int</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">bbcode</span> <span class="p">=</span> <span class="s">"[img=</span><span class="si">\(</span><span class="n">width</span><span class="si">)</span><span class="s">x</span><span class="si">\(</span><span class="n">height</span><span class="si">)</span><span class="s">]</span><span class="si">\(</span><span class="n">link</span><span class="si">)</span><span class="s">[/img]</span><span class="se">\n</span><span class="s">"</span>
<span class="c1">//print(bbcode)</span>
<span class="c1">// 將圖片網址插入輸入框的程式 textViewInsert() 寫在下面</span>
<span class="kc">self</span><span class="p">.</span><span class="n">textViewInsert</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">bbcode</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// 在 textView 游標點選的地方插入字串,沒有點選的話會加在最後面</span>
<span class="kd">func</span> <span class="nf">textViewInsert</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">range</span> <span class="p">=</span> <span class="n">textTextView</span><span class="p">.</span><span class="n">selectedTextRange</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">textTextView</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="n">range</span><span class="p">,</span> <span class="n">withText</span><span class="p">:</span> <span class="n">string</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
<span style="color:#00F000">設定語系</span>
在專案設定的「Info」,將「Localization native development region」
由「en」改為「Taiwan」
<div class="img" data-ori_w="1442" data-ori_h="704" style="width:721px;height:352px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/l6CcNqK.png" alt="[圖]" /></div>
這樣選擇圖片時就會顯示中文了
<div class="img" data-ori_w="522" data-ori_h="402" style="width:261px;height:201px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Y7ujNb6.png" alt="[圖]" /></div>
參考
<a href="http://www.codingexplorer.com/choosing-images-with-uiimagepickercontroller-in-swift/" target="_blank" rel="nofollow">Choosing Images with UIImagePickerController in Swift</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-07 23:48:57</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-07-19 18:09:30</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 Unwind 跳至之前任一個 ViewController
http://disp.cc/b/11-a1iH
2017-05-06T00:56:24+08:00
2017-05-06T19:46:35+08:00
例如從文章列表頁 → 閱讀文章頁 → 編輯文章頁
TextListViewController → TextViewController → EditorViewController
如果是使用「回覆文章」,希望能在編輯文章頁點了「發佈文章」後,
不要回到閱讀文章頁,而是直接跳回文章列表頁,並執行 refresh() 重整資料
先在 TextListViewController 加上一個 @IBAction unwindToTextList
@IBAction func unwindToTextList(segue: UIStoryboardSegue) {
refresh(self)
}
裡面可以加上跳回來時要執行的程式
例如使用 refresh() 重整文章列表,讓新的文章顯示出來
在編輯文章頁,按著 Ctrl 將 Button 或 Controller 拉至 Exit
跳出的選單選「unwindToTextListWithSegue:」
會產生一個 Unwind segue
因為這邊沒有 Button 可以拉,所以使用 Controller 拉
之 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Unwind 跳至之前任一個 ViewController<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-06 Sat. 00:56:25</div><hr color="#008080" />───────────────────
例如從文章列表頁 → 閱讀文章頁 → 編輯文章頁
TextListViewController → TextViewController → EditorViewController
<div class="img" data-ori_w="1415" data-ori_h="791" style="width:708px;height:396px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/2k2h5MG.png" alt="[圖]" /></div>
如果是使用「回覆文章」,希望能在編輯文章頁點了「發佈文章」後,
不要回到閱讀文章頁,而是直接跳回文章列表頁,並執行 refresh() 重整資料
先在 TextListViewController 加上一個 @IBAction unwindToTextList
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">unwindToTextList</span><span class="p">(</span><span class="n">segue</span><span class="p">:</span> <span class="bp">UIStoryboardSegue</span><span class="p">)</span> <span class="p">{</span>
<span class="n">refresh</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
<span class="p">}</span>
</div>
裡面可以加上跳回來時要執行的程式
例如使用 refresh() 重整文章列表,讓新的文章顯示出來
在編輯文章頁,按著 Ctrl 將 Button 或 Controller 拉至 Exit
跳出的選單選「unwindToTextListWithSegue:」
會產生一個 Unwind segue
<div class="img" data-ori_w="856" data-ori_h="181" style="width:428px;height:91px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/e65DCFj.png" alt="[圖]" /></div>
因為這邊沒有 Button 可以拉,所以使用 Controller 拉
之後再用程式執行換頁
使用 Controller 拉 Segue 的話,會出現警告
要求 Segue 必需加上 Identifier
點一下剛剛產生的 Unwind segue
輸入 Identifier 為「UnwindToTextList」
<div class="img" data-ori_w="1134" data-ori_h="502" style="width:567px;height:251px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hoDjl3A.png" alt="[圖]" /></div>
修改 EditorViewController.swift
在回覆文章完成後,執行
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">performSegue</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"UnwindToTextList"</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
</div>
即可直接跳至文章列表頁,並執行文章列表頁的 @IBAction unwindToTextList()
參考
StackOverflow <a href="http://stackoverflow.com/questions/12561735/what-are-unwind-segues-for-and-how-do-you-use-them" target="_blank" rel="nofollow">What are Unwind segues for and how do you use them?</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-06 00:56:25</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-06 19:46:42</span></div></pre>
Knuckles
[Xcode][Swift3] 建立文章編輯器
http://disp.cc/b/11-a0P2
2017-05-02T16:25:51+08:00
2017-05-06T23:41:39+08:00
使用 ScrollView 來建立一個文章編輯器
用來編輯多行文章的 Text View 要能夠隨螢幕大小調整
虛擬鍵盤跳出來時 Scroll View 要能縮小高度到可顯示的範圍
拉一個新的 Navigation Controller,將附帶的 Table View Controller 刪除
再拉一個新的 View Controller
加上 Segue 連結兩個 Controller
按著 Ctrl 將 Navigation Controller 拉到 View Controller
跳出的選單選擇「root view controller」
在文章列表頁的右上方加上發表文章的按鈕
按著 Ctrl 將按鈕拉至新的 Navigation Controller
跳出的選單選擇「Present Modally」
因為使用了新 Navigation Controller
開啟這頁後,左上角不會自動出現「回上頁」的按鈕
要自己加上去,拉一個 Bar Button Item 到左上角
Title 輸入「取消」
新增一個類別檔案 EditorViewController.swift
Subc ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 新增文章編輯器<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-02 Tue. 16:25:51</div><hr color="#008080" />───────────────────
使用 ScrollView 來建立一個文章編輯器
用來編輯多行文章的 Text View 要能夠隨螢幕大小調整
虛擬鍵盤跳出來時 Scroll View 要能縮小高度到可顯示的範圍
拉一個新的 Navigation Controller,將附帶的 Table View Controller 刪除
再拉一個新的 View Controller
<div class="img" data-ori_w="1156" data-ori_h="534" style="width:578px;height:267px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/KESIQZx.png" alt="[圖]" /></div>
加上 Segue 連結兩個 Controller
按著 Ctrl 將 Navigation Controller 拉到 View Controller
跳出的選單選擇「root view controller」
<div class="img" data-ori_w="692" data-ori_h="342" style="width:346px;height:171px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Nlduavq.png" alt="[圖]" /></div>
在文章列表頁的右上方加上發表文章的按鈕
按著 Ctrl 將按鈕拉至新的 Navigation Controller
跳出的選單選擇「Present Modally」
<div class="img" data-ori_w="976" data-ori_h="583" style="width:488px;height:292px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/s4Gq7PT.png" alt="[圖]" /></div>
因為使用了新 Navigation Controller
開啟這頁後,左上角不會自動出現「回上頁」的按鈕
要自己加上去,拉一個 Bar Button Item 到左上角
Title 輸入「取消」
<div class="img" data-ori_w="597" data-ori_h="246" style="width:299px;height:123px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/iuQnpEx.png" alt="[圖]" /></div>
新增一個類別檔案 EditorViewController.swift
Subclass of: ViewController
設定新的 ViewController 的自訂類別為 EditorViewController
使用 AssisantEditor 拉一個 @IBAction 到 EditorViewController.swift
名稱輸入「cancel」,將產生的 @IBAction 改為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">cancel</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 先關掉虛擬鍵盤</span>
<span class="kc">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">endEditing</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="bp">UIAlertController</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"取消編輯"</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">"確定要不存檔離開嗎?"</span><span class="p">,</span> <span class="n">preferredStyle</span><span class="p">:</span> <span class="p">.</span><span class="n">alert</span><span class="p">)</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"取消"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="n">cancel</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"確定"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">action</span> <span class="k">in</span>
<span class="kc">self</span><span class="p">.</span><span class="n">dismiss</span><span class="p">(</span><span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}))</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
點了取消按鈕後,會跳出確認對話框,點了確定後,
執行 self.dismiss(animated:completion:) 即可關閉目前這一頁
回到之前的頁面
在新的 View Controller 中拉一個 Scroll View
調整成顯示範圍的大小,加上四個方向皆為 0 的 Constraints
<div class="img" data-ori_w="985" data-ori_h="790" style="width:493px;height:395px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oSoOpN0.png" alt="[圖]" /></div>
在 Scroll View 中再拉一個 View
調整成與 Scroll View 的大小,加上四個方向皆為 0 的 Constraints
<div class="img" data-ori_w="1113" data-ori_h="818" style="width:557px;height:409px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/eCixySr.png" alt="[圖]" /></div>
然後將這個 View 改名為 Content View
在 Scroll View 中的 View,必需要有六個 Constraints 才行
還缺長跟寬的,但長跟寬不是固定的數值,必需要與顯示範圍一樣大
先取消勾選 Navigation Bar 的 Translucent 屬性
避免顯示範圍覆蓋到 Navigation Bar 的位置
<div class="img" data-ori_w="1440" data-ori_h="491" style="width:720px;height:246px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YnFIqXY.png" alt="[圖]" /></div>
在左邊的 Document Outline
按著 Ctrl 將 Scroll View 裡面的 View 拉到 Scroll View
選擇「Equal Widths」,然後再拉一次選擇「Equal Heights」
<div class="img" data-ori_w="792" data-ori_h="366" style="width:396px;height:183px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/bYmXmMk.png" alt="[圖]" /></div>
這樣 Scroll View 裡的 View 就會保持和 Scroll View 一樣大
在 Scroll View 裡的 View 加上想要的 Label 與 輸入框
其中 Text View 要加上四個方向為 0 的 Constraints
<div class="img" data-ori_w="938" data-ori_h="806" style="width:469px;height:403px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7AxnHbZ.png" alt="[圖]" /></div>
執行看看,在 Text View 中輸入很多行的文字後
發現跳出來的虛擬鍵盤會蓋住後面的文字內容
必需要在虛擬鍵盤出現時,將 Scroll View 的高度縮小才行
找出 Scroll View 與下方邊界的 Constraint
<div class="img" data-ori_w="933" data-ori_h="699" style="width:467px;height:350px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/pV4BB34.png" alt="[圖]" /></div>
使用 Assistant Editor 將這個 Constraint
在 EditorViewController.swift 中加上一個 @IBLayout
名稱輸入「scrollViewBottomConstraint」
要使用程式將這個 Constraint 的值設為鍵盤的高度
找出 Content View 高度的 Constraint
<div class="img" data-ori_w="925" data-ori_h="688" style="width:463px;height:344px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Ta3tFAS.png" alt="[圖]" /></div>
一樣使用 Assistant Editor 拉至 EditorViewController.swift 中
建立 @IBLayout,名稱輸入「contentViewHeight」
要在鍵盤出現時,將 Content View 的高度設定為比 Scroll View 多 60
也就是上面「看板:」與「標題:」的高度
這樣就可以往下捲動,將「看板:」與「標題:」隱藏了
編輯 EditorViewController.swift
加上成員函數 viewWillAppear()
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewWillAppear</span><span class="p">(</span><span class="kc">_</span> <span class="n">animated</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewWillAppear</span><span class="p">(</span><span class="n">animated</span><span class="p">)</span>
<span class="bp">NSNotificationCenter</span><span class="p">.</span><span class="n">defaultCenter</span><span class="p">().</span><span class="n">addObserver</span><span class="p">(</span>
<span class="kc">self</span><span class="p">,</span>
<span class="n">selector</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">keyboardWillShow</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span>
<span class="n">name</span><span class="p">:</span> <span class="n">UIKeyboardWillShowNotification</span><span class="p">,</span>
<span class="n">object</span><span class="p">:</span> <span class="kc">nil</span>
<span class="p">)</span>
<span class="bp">NSNotificationCenter</span><span class="p">.</span><span class="n">defaultCenter</span><span class="p">().</span><span class="n">addObserver</span><span class="p">(</span>
<span class="kc">self</span><span class="p">,</span>
<span class="n">selector</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">keyboardWillHide</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span>
<span class="n">name</span><span class="p">:</span> <span class="n">UIKeyboardWillHideNotification</span><span class="p">,</span>
<span class="n">object</span><span class="p">:</span> <span class="kc">nil</span>
<span class="p">)</span>
</div>
使用 NSNotificationCenter 加上通知
在鍵盤出現前執行 self.keyboardWillShow(_:)
在鍵盤關閉前執行 self.keyboardWillHide(_:)
加上成員函數 viewWillDisappear()
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewWillDisappear</span><span class="p">(</span><span class="kc">_</span> <span class="n">animated</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewWillDisappear</span><span class="p">(</span><span class="n">animated</span><span class="p">)</span>
<span class="bp">NSNotificationCenter</span><span class="p">.</span><span class="n">defaultCenter</span><span class="p">().</span><span class="n">removeObserver</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
<span class="p">}</span>
</div>
在編輯器關閉時停止通知
加上兩個成員函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Keyboard Action</span>
<span class="kd">func</span> <span class="nf">keyboardWillShow</span><span class="p">(</span><span class="kc">_</span> <span class="n">notification</span><span class="p">:</span> <span class="bp">NSNotification</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">value</span> <span class="p">=</span> <span class="n">notification</span><span class="p">.</span><span class="n">userInfo</span><span class="p">?[</span><span class="n">UIKeyboardFrameBeginUserInfoKey</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="bp">NSValue</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="kd">let</span> <span class="nv">keyboardHeight</span> <span class="p">=</span> <span class="n">value</span><span class="p">.</span><span class="n">cgRectValue</span><span class="p">.</span><span class="n">height</span>
<span class="kc">self</span><span class="p">.</span><span class="n">scrollViewBottomConstraint</span><span class="p">.</span><span class="n">constant</span> <span class="p">=</span> <span class="n">keyboardHeight</span>
<span class="kc">self</span><span class="p">.</span><span class="n">contentViewHeight</span><span class="p">.</span><span class="n">constant</span> <span class="p">=</span> <span class="mi">60</span>
<span class="kc">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">layoutIfNeeded</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">keyboardWillHide</span><span class="p">(</span><span class="kc">_</span> <span class="n">notification</span><span class="p">:</span> <span class="bp">NSNotification</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">scrollViewBottomConstraint</span><span class="p">.</span><span class="n">constant</span> <span class="p">=</span> <span class="mi">0</span>
<span class="kc">self</span><span class="p">.</span><span class="n">contentViewHeight</span><span class="p">.</span><span class="n">constant</span> <span class="p">=</span> <span class="mi">0</span>
<span class="p">}</span>
</div>
當鍵盤顯示時,將 Scroll View 與下方的距離設為鍵盤的高度
將 Content View 的高度設為比 Scroll View 多 60
當鍵盤隱藏時,再將設定改回來
如果想要在點擊 Text View 時,Scroll View 自動往下捲動,
將「看板:」與「標題:」隱藏在上方的話
使用 Assistant Editor 將 Scroll View 拉至 EditorViewController.swift 中
建立 @IBLayout,名稱輸入「scrollView」
按著 Ctrl 將 Text View 拉至 View Controller
選擇 delegate
<div class="img" data-ori_w="528" data-ori_h="514" style="width:264px;height:257px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7ia8WSl.png" alt="[圖]" /></div>
編輯 EditorViewController.swift
將類別加上繼承 UITextViewDelegate
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">EditorViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">,</span> <span class="bp">UITextViewDelegate</span> <span class="p">{</span>
</div>
加上 delegate 函數 textViewDidBeginEditing()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">textViewDidBeginEditing</span><span class="p">(</span><span class="kc">_</span> <span class="n">textView</span><span class="p">:</span> <span class="bp">UITextView</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">bottomOffset</span> <span class="p">=</span> <span class="n">CGPoint</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">scrollView</span><span class="p">.</span><span class="n">contentSize</span><span class="p">.</span><span class="n">height</span> <span class="o">-</span> <span class="kc">self</span><span class="p">.</span><span class="n">scrollView</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">scrollView</span><span class="p">.</span><span class="n">setContentOffset</span><span class="p">(</span><span class="n">bottomOffset</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
</div>
在點擊了 Text View 後,Scroll View 往下捲動
將「看板:」與「標題:」隱藏在上方
執行結果
<div class="img" data-ori_w="954" data-ori_h="800" style="width:477px;height:400px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/bv1v8dx.png" alt="[圖]" /></div>
<span style="color:#00F000">要從文章列表傳值到編輯器時</span>
例如要傳看板名稱 boardName 過去
在 EditorViewController 新增成員變數 boardName
點選文章列表連至 EditorViewController 的 Segue
設定 Identifier 為「Post」
修改文章列表的成員函數 prepare(for:sender:) 加上
<div class="highlight"><span></span> <span class="k">if</span> <span class="n">segue</span><span class="p">.</span><span class="n">identifier</span> <span class="p">==</span> <span class="s">"Post"</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">navigationController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">!</span> <span class="bp">UINavigationController</span>
<span class="kd">let</span> <span class="nv">editorViewController</span> <span class="p">=</span> <span class="n">navigationController</span><span class="p">.</span><span class="n">topViewController</span> <span class="k">as</span><span class="p">!</span> <span class="n">EditorViewController</span>
<span class="n">editorViewController</span><span class="p">.</span><span class="n">boardName</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardName</span>
<span class="p">}</span>
</div>
<span style="color:#00F000">點擊非輸入區的時候隱藏鍵盤</span>
拉一個 Tap Gesture Recognizer 到 root View
<div class="img" data-ori_w="1309" data-ori_h="644" style="width:655px;height:322px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/X6I7ujy.png" alt="[圖]" /></div>
使用 Assistant Editor 將 Tap Gesture Recognizer 建立一個 @IBAction
名稱輸入「hideKeyboard」
在建立的 @IBAction 函數 hideKeyboard() 裡加上
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">endEditing</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
</div>
執行看看,先點一下輸入框讓虛擬鍵盤跳出來
再點一下上方的「看板:」或「標題:」,虛擬鍵盤就會關閉了
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-02 16:25:51</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-06 23:41:46</span></div></pre>
Knuckles
[Xcode][Swift3] 加入Google提供的圖示 Material icons
http://disp.cc/b/11-a0JJ
2017-05-01T20:34:51+08:00
2017-05-05T15:17:13+08:00
https://material.io/icons/
是 Google 提供的免費圖示網站
需要圖示的時候可以在這邊找找看有沒有適合的
下載到 App 的專案中使用
例如想要在右上角的 Navigation Bar 加上 More 按鈕「…」
在 https://material.io/icons/ 搜尋 more
選擇想要圖示後,在下方選擇大小為 36dp,顏色為白色
下載 PNG 檔
補充: 36dp 會有點大,改用預設的 24dp 比較適合
將下載的檔案解壓縮後,在 ios 資料夾裡
會有個「ic_more_horiz_white_36pt.imageset」檔
將這個檔案拉到專案的 Assets.xcassets 中
拉一個 Bar Button Item 到 Navigation Bar 右邊
設定 Image 為「ic_more_horiz_white_36pt」即可
覺得自訂圖示的左右間距太寬的話 參考 StackOverflow
可以將圖示拉一個 @IBLayout ,名稱輸入「moreButton」
然後在 viewDidLoad() 裡加入
more ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加入Google提供的圖示 Material icons<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-05-01 Mon. 20:34:52</div><hr color="#008080" />───────────────────
<a href="https://material.io/icons/" target="_blank" rel="nofollow">https://material.io/icons/</a>
是 Google 提供的免費圖示網站
需要圖示的時候可以在這邊找找看有沒有適合的
下載到 App 的專案中使用
例如想要在右上角的 Navigation Bar 加上 More 按鈕「…」
在 <a href="https://material.io/icons/" target="_blank" rel="nofollow">https://material.io/icons/</a> 搜尋 more
選擇想要圖示後,在下方選擇大小為 36dp,顏色為白色
下載 PNG 檔
<div class="img" data-ori_w="1266" data-ori_h="740" style="width:633px;height:370px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7D8zhAW.png" alt="[圖]" /></div>
補充: 36dp 會有點大,改用預設的 24dp 比較適合
將下載的檔案解壓縮後,在 ios 資料夾裡
會有個「ic_more_horiz_white_36pt.imageset」檔
<div class="img" data-ori_w="994" data-ori_h="150" style="width:497px;height:75px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hdVRVeW.png" alt="[圖]" /></div>
將這個檔案拉到專案的 Assets.xcassets 中
<div class="img" data-ori_w="1635" data-ori_h="623" style="width:818px;height:312px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/APyBoj0.png" alt="[圖]" /></div>
拉一個 Bar Button Item 到 Navigation Bar 右邊
設定 Image 為「ic_more_horiz_white_36pt」即可
<div class="img" data-ori_w="1021" data-ori_h="402" style="width:511px;height:201px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/N0JvQn5.png" alt="[圖]" /></div>
覺得自訂圖示的左右間距太寬的話 參考 <a href="http://stackoverflow.com/questions/22741824/how-to-adjust-space-between-two-uibarbuttonitem-in-rightbarbuttonitems" target="_blank" rel="nofollow">StackOverflow</a>
可以將圖示拉一個 @IBLayout ,名稱輸入「moreButton」
然後在 viewDidLoad() 裡加入
<div class="highlight"><span></span> <span class="n">moreButton</span><span class="p">.</span><span class="n">imageInsets</span> <span class="p">=</span> <span class="n">UIEdgeInsetsMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">15</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</div>
執行結果
<div class="img" data-ori_w="618" data-ori_h="424" style="width:309px;height:212px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/NncYMSu.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-05-01 20:34:52</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-05 15:17:20</span>
補充; 這是pt 而且是bt站的一種...會紀算你的流量
文章作者可對每則推文做回應...
文章作者可對每則推文做回應...
</div></pre>
Knuckles
[Xcode][Swift3] 使用 Keychain 儲存機密資料
http://disp.cc/b/11-a0tB
2017-04-29T21:34:02+08:00
2017-04-29T22:19:53+08:00
如果想要做保持登入的功能的話,
需要將使用者的帳號密碼儲存起來
但密碼如果使用 UserDefaults 來儲存的話不安全,
可能會在 JB 後被其他的 App 竊取,
安全的做法是使用 Keychain (鑰匙圈) 來儲存
若手機的設定有開啟 iCloud 同步鑰匙圈的話,
儲存在 Keychain 的資料還可以與其他的裝置同步更新
但使用 Keychain 的方法有點麻煩
所以要安裝第三方程式來處理
安裝 KeychainSwift
參考 GitHub https://github.com/marketplacer/keychain-swift
iOS7+ 可以下載 KeychainSwiftDistrib.swift 檔,
放到專案中就可以了
iOS8+ 可以使用 CocoaPods 安裝
在 Podfile 加入
pod 'KeychainSwift', '~> 8.0'
後,在專案目錄執行 pod update
CocoaPods 的使用方法可參考: [Xcode][Swift3] 安裝套件管理工具 CocoaPods - Knu ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Keychain 儲存機密資料<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-29 Sat. 21:33:03</div><hr color="#008080" />───────────────────
如果想要做保持登入的功能的話,
需要將使用者的帳號密碼儲存起來
但密碼如果使用 UserDefaults 來儲存的話不安全,
可能會在 JB 後被其他的 App 竊取,
安全的做法是使用 Keychain (鑰匙圈) 來儲存
若手機的設定有開啟 iCloud 同步鑰匙圈的話,
儲存在 Keychain 的資料還可以與其他的裝置同步更新
但使用 Keychain 的方法有點麻煩
所以要安裝第三方程式來處理
<span style="color:#00F000">安裝 KeychainSwift</span>
參考 GitHub <a href="https://github.com/marketplacer/keychain-swift" target="_blank" rel="nofollow">https://github.com/marketplacer/keychain-swift</a>
iOS7+ 可以下載 <a href="https://github.com/marketplacer/keychain-swift/blob/master/Distrib/KeychainSwiftDistrib.swift" target="_blank" rel="nofollow">KeychainSwiftDistrib.swift</a> 檔,
放到專案中就可以了
iOS8+ 可以使用 CocoaPods 安裝
在 Podfile 加入
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'KeychainSwift'</span><span class="p">,</span><span class="w"> </span><span class="s1">'~> 8.0'</span>
</div>
後,在專案目錄執行 pod update
CocoaPods 的使用方法可參考: <a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS</a>
<span style="color:#00F000">使用 KeychainSwift</span>
檔案前面先加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">KeychainSwift</span>
</div>
要存取字串資料時,使用
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">myPassword</span> <span class="p">=</span> <span class="s">"secret password"</span>
<span class="kd">let</span> <span class="nv">keychain</span> <span class="p">=</span> <span class="n">KeychainSwift</span><span class="p">()</span>
<span class="c1">// 儲存密碼至 keychain</span>
<span class="n">keychain</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">myPassword</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="s">"savedPassword"</span><span class="p">)</span>
<span class="c1">// 要讀取的時候使用</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">getPassword</span> <span class="p">=</span> <span class="n">keychain</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s">"savedPassword"</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="n">getPassword</span><span class="p">)</span>
<span class="p">}</span>
</div>
使用 keychain.set(_:forKey:) 可以傳入 String, Bool, Object
讀取時分別要使用
<div class="highlight"><span></span> <span class="c1">// String</span>
<span class="n">keychain</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s">"myString"</span><span class="p">)</span>
<span class="c1">// Bool</span>
<span class="n">keychain</span><span class="p">.</span><span class="n">getBool</span><span class="p">(</span><span class="s">"myBoolean"</span><span class="p">)</span>
<span class="c1">// Object</span>
<span class="n">keychain</span><span class="p">.</span><span class="n">getData</span><span class="p">(</span><span class="s">"myObject"</span><span class="p">)</span>
</div>
要清除資料時使用
<div class="highlight"><span></span> <span class="c1">// 刪除某一筆資料</span>
<span class="n">keychain</span><span class="p">.</span><span class="n">delete</span><span class="p">(</span><span class="s">"savedPassword"</span><span class="p">)</span>
<span class="c1">// 清空所有資料</span>
<span class="n">keychain</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
</div>
要與其他裝置同步資料的話,在使用 set() 與 get() 前執行
<div class="highlight"><span></span> <span class="n">keychain</span><span class="p">.</span><span class="n">synchronizable</span> <span class="p">=</span> <span class="kc">true</span>
</div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-29 21:33:03</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-29 22:18:54</span></div></pre>
Knuckles
[Xcode][Swift3] Alamofire 儲存 Cookies 資料
http://disp.cc/b/11-a0mO
2017-04-28T22:23:24+08:00
2017-04-29T11:30:44+08:00
參考 [Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS
使用 Alamofire 存取網站時,
會將 Cookies 資料存在系統的 HTTPCookieStorage
下一次連線時會使用同樣的 Cookies 來連線
若是使用 WebView 載入網頁也會用相同的 Cookies
但若是將 App 強制關閉再重新打開後,
儲存的 Cookies 資料就會消失了
如果想要做保持帳號登入的功能,
可以將 Cookies 先使用 UserDefaults 存起來
重新開啟時再讀出來存進 HTTPCookieStorage
Alamofire 版本 4.4
在登入帳號的頁面,建立一個成員函數 saveCookies()
func saveCookies(response: DataResponse<Any>) {
let headerFields = response.response?.allHeaderFields as! [String: String]
let url = resp ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] Alamofire 儲存 Cookies 資料<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-28 Fri. 22:22:27</div><hr color="#008080" />───────────────────
參考 <a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
使用 Alamofire 存取網站時,
會將 Cookies 資料存在系統的 HTTPCookieStorage
下一次連線時會使用同樣的 Cookies 來連線
若是使用 WebView 載入網頁也會用相同的 Cookies
但若是將 App 強制關閉再重新打開後,
儲存的 Cookies 資料就會消失了
如果想要做保持帳號登入的功能,
可以將 Cookies 先使用 UserDefaults 存起來
重新開啟時再讀出來存進 HTTPCookieStorage
Alamofire 版本 4.4
在登入帳號的頁面,建立一個成員函數 saveCookies()
<div class="highlight"><span></span><span class="kd">func</span> <span class="nf">saveCookies</span><span class="p">(</span><span class="n">response</span><span class="p">:</span> <span class="n">DataResponse</span><span class="p"><</span><span class="nb">Any</span><span class="p">>)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">headerFields</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">response</span><span class="p">?.</span><span class="n">allHeaderFields</span> <span class="k">as</span><span class="p">!</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">String</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">response</span><span class="p">?.</span><span class="n">url</span>
<span class="kd">let</span> <span class="nv">cookies</span> <span class="p">=</span> <span class="n">HTTPCookie</span><span class="p">.</span><span class="n">cookies</span><span class="p">(</span><span class="n">withResponseHeaderFields</span><span class="p">:</span> <span class="n">headerFields</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">url</span><span class="p">!)</span>
<span class="kd">var</span> <span class="nv">cookieArray</span> <span class="p">=</span> <span class="p">[[</span><span class="n">HTTPCookiePropertyKey</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]]()</span>
<span class="k">for</span> <span class="n">cookie</span> <span class="k">in</span> <span class="n">cookies</span> <span class="p">{</span>
<span class="n">cookieArray</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">cookie</span><span class="p">.</span><span class="n">properties</span><span class="p">!)</span>
<span class="p">}</span>
<span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">cookieArray</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="s">"savedCookies"</span><span class="p">)</span>
<span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="n">synchronize</span><span class="p">()</span>
<span class="p">}</span>
</div>
從網站的回應中讀取 Cookies 資料,
將 Cookies 轉為 Cookie 陣列,
存入 UserDefaults 的 "savedCookies"
使用 Alamofire 登入網站後,執行 saveCookies
<div class="highlight"><span></span><span class="n">Alamofire</span><span class="p">.</span><span class="n">request</span><span class="p">(</span><span class="n">urlString</span><span class="p">).</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="n">saveCookies</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="p">}</span>
</div>
在 App 一開始的主頁面,建立一個成員函數 loadCookies()
<div class="highlight"><span></span><span class="kd">func</span> <span class="nf">loadCookies</span><span class="p">()</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">cookieArray</span> <span class="p">=</span> <span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">"savedCookies"</span><span class="p">)</span> <span class="k">as</span><span class="p">?</span> <span class="p">[[</span><span class="n">HTTPCookiePropertyKey</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]]</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">for</span> <span class="n">cookieProperties</span> <span class="k">in</span> <span class="n">cookieArray</span> <span class="p">{</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">cookie</span> <span class="p">=</span> <span class="n">HTTPCookie</span><span class="p">(</span><span class="n">properties</span><span class="p">:</span> <span class="n">cookieProperties</span><span class="p">)</span> <span class="p">{</span>
<span class="n">HTTPCookieStorage</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">setCookie</span><span class="p">(</span><span class="n">cookie</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
從 UserDefaults 中取出 Cookie 陣列
將每個 Cookie 分別存入 HTTPCookieStorage
只要執行一次 loadCookies() 即可
之後使用 Alamofire.request() 或是用 WebView 載入網頁時,
都會使用這組 Cookies 來連線了
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-28 22:22:27</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-29 11:29:46</span></div></pre>
Knuckles
[Xcode][Swift3] 使用協定(Protocol)建立委派(Delegate)函數
http://disp.cc/b/11-a0lX
2017-04-28T19:20:24+08:00
2017-04-28T19:23:13+08:00
委派函數(delegate function)
是一種會在別的類別裡呼叫,但函數內容是寫在自己類別裡的函數
例如我們要做一個登入頁的功能
在主頁面 MainViewController 有個成員函數 didLogin()
可以將登入按鈕的文字改為登出
在主頁面點了登入按鈕後,會跳至登入頁 LoginViewController
在登入頁中,輸入帳號密碼送出後
希望能執行主頁面的 didLogin()
將主頁面的登入鈕改成登出鈕
以下為實作的步驟
修改登入頁的 LoginViewController.swift
在 class LoginViewController: UIViewController { 這行前面加上
protocol LoginViewControllerDelegate {
func didLogin(userId: Int, userName: String)
}
建立一個協定(Protocol),名稱為 LoginViewControllerDelegate
協定內容為,需要建立一個委派函數 didLogin()
在類別加上一個成員函數
var ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用協定(Protocol)建立委派(Delegate)函數<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-28 Fri. 19:20:23</div><hr color="#008080" />───────────────────
委派函數(delegate function)
是一種會在別的類別裡呼叫,但函數內容是寫在自己類別裡的函數
例如我們要做一個登入頁的功能
<div class="img" data-ori_w="1274" data-ori_h="561" style="width:637px;height:281px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kF4X22h.png" alt="[圖]" /></div>
在主頁面 MainViewController 有個成員函數 didLogin()
可以將登入按鈕的文字改為登出
在主頁面點了登入按鈕後,會跳至登入頁 LoginViewController
在登入頁中,輸入帳號密碼送出後
希望能執行主頁面的 didLogin()
將主頁面的登入鈕改成登出鈕
以下為實作的步驟
修改登入頁的 LoginViewController.swift
在 class LoginViewController: UIViewController { 這行前面加上
<div class="highlight"><span></span><span class="kd">protocol</span> <span class="nc">LoginViewControllerDelegate</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">didLogin</span><span class="p">(</span><span class="n">userId</span><span class="p">:</span> <span class="nb">Int</span><span class="p">,</span> <span class="n">userName</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span>
<span class="p">}</span>
</div>
建立一個協定(Protocol),名稱為 LoginViewControllerDelegate
協定內容為,需要建立一個委派函數 didLogin()
在類別加上一個成員函數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">delegate</span><span class="p">:</span> <span class="n">LoginViewControllerDelegate</span><span class="p">?</span>
</div>
用來代表會用到這個協定的類別
在完成登入,取得使用者ID與名稱後,執行委派函數
並傳入取得的ID與名稱
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">delegate</span><span class="p">?.</span><span class="n">didLogin</span><span class="p">(</span><span class="n">userId</span><span class="p">:</span> <span class="n">userId</span><span class="p">,</span> <span class="n">userName</span><span class="p">:</span> <span class="n">userName</span><span class="p">)</span>
</div>
修改主頁面的 MainViewController.swift
將 class MainViewController: UIViewController { 改為
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">MainViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">,</span> <span class="n">LoginViewControllerDelegate</span> <span class="p">{</span>
</div>
代表要使用協定 LoginViewControllerDelegate
這時會跳出錯誤訊息:
Type 'MainViewController' does not conform to protocol 'LoginViewControllerDelegate'
因為協定的內容是必需建立一個委派函數
加上成員函數 didLogin()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">didLogin</span><span class="p">(</span><span class="n">userId</span><span class="p">:</span> <span class="nb">Int</span><span class="p">,</span> <span class="n">userName</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userId</span> <span class="p">=</span> <span class="n">userId</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userName</span> <span class="p">=</span> <span class="n">userName</span>
<span class="kc">self</span><span class="p">.</span><span class="n">loginButton</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="s">"登出"</span>
<span class="p">}</span>
</div>
將登入頁取得的ID與名稱存到這個類別的成員變數
並將登入按鈕的顯示文字改為登出
修改成員函數 prepare()
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">prepare</span><span class="p">(</span><span class="k">for</span> <span class="n">segue</span><span class="p">:</span> <span class="bp">UIStoryboardSegue</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">segue</span><span class="p">.</span><span class="n">identifier</span> <span class="p">==</span> <span class="s">"LoginSegue"</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">loginViewController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">!</span> <span class="n">LoginViewController</span>
<span class="n">loginViewController</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
在 storyboard 將點擊登入鈕連至登入頁的 Segue 設定 Identifier 為「LoginSegue」
在使用 Segue 跳頁時將主頁面的位址存在登入頁的成員變數 delegate
這樣登入頁才能使用 delegate.didLogin()
執行主頁面的成員函數 didLogin()
寫成協定的好處是,登入頁的協定也可以用在別的頁面上
此時委派函數 didLogin() 就可以依不同的頁面實作不同的內容
例如登入後要在某個 Label 顯示使用者名稱之類的
參考
StackOverflow <a href="http://stackoverflow.com/questions/24099230/delegates-in-swift" target="_blank" rel="nofollow">Delegates in swift?</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-28 19:20:23</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-28 19:23:13</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 UserDefaults 儲存資料
http://disp.cc/b/11-a0k2
2017-04-28T14:01:32+08:00
2017-04-29T20:54:09+08:00
如果只是要存一些簡單的資料,像是使用者名稱、輸入過的值
需要在 App 強制關閉再重開時還能保留時
可以使用 UserDefaults
在成員變數新增一個 userDefault
let userDefault = UserDefaults.standard
儲存資料
let userName = "knuckles"
self.userDefault.set(userName, forKey: "userName")
self.userDefault.synchronize()
要執行 .synchronize() 才會真的寫入資料中
讀取資料
if let userName = self.userDefault.string(forKey: "userName") {
print("userName: \(userName)")
}
刪除資料
self.userDefault.removeObject(forKey: "u ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 UserDefaults 儲存資料<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-28 Fri. 14:00:35</div><hr color="#008080" />───────────────────
如果只是要存一些簡單的資料,像是使用者名稱、輸入過的值
需要在 App 強制關閉再重開時還能保留時
可以使用 UserDefaults
在成員變數新增一個 userDefault
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">userDefault</span> <span class="p">=</span> <span class="n">UserDefaults</span><span class="p">.</span><span class="n">standard</span>
</div>
儲存資料
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">userName</span> <span class="p">=</span> <span class="s">"knuckles"</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">userName</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="s">"userName"</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="n">synchronize</span><span class="p">()</span>
</div>
要執行 .synchronize() 才會真的寫入資料中
讀取資料
<div class="highlight"><span></span> <span class="k">if</span> <span class="kd">let</span> <span class="nv">userName</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="n">string</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">"userName"</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"userName: </span><span class="si">\(</span><span class="n">userName</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
</div>
刪除資料
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="n">removeObject</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">"userName"</span><span class="p">)</span>
</div>
陣列可以直接存
<div class="highlight"><span></span> <span class="c1">// save Array</span>
<span class="kd">let</span> <span class="nv">userList</span> <span class="p">=</span> <span class="p">[</span><span class="s">"aaa"</span><span class="p">,</span><span class="s">"bbb"</span><span class="p">,</span><span class="s">"ccc"</span><span class="p">]</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span><span class="n">userList</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="s">"userList"</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="n">synchronize</span><span class="p">()</span>
<span class="c1">// load Array</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">userList</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">userDefault</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="s">"userList"</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"userList:</span><span class="si">\(</span><span class="n">userList</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
</div>
參考
Swift起步走 <a href="https://itisjoe.gitbooks.io/swiftgo/content/uikit/nsuserdefaults.html" target="_blank" rel="nofollow">儲存資訊 NSUserDefaults</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-28 14:00:35</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-29 20:53:10</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 CoreData 儲存資料
http://disp.cc/b/11-9ZSr
2017-04-24T02:26:39+08:00
2017-05-07T20:31:52+08:00
要儲存資料,讓 APP 強制關掉再打開後資料還在的話
若資料只是單純的 key-value 值的話,可以使用 NSUserDefaults 就好
若是複雜的資料要用到資料庫的話,就要使用 Core Data 了
在 CoreData 底層是使用 SQLite 這個資料庫來儲存資料
只是把 SQL 的指令用物件導向包裝起來
在專案加上 Core Data
在開新專案時,如果有勾選「Use Core Data」
會在檔案列表加上一個 XXXX.xcdatamodeld 的檔案
以及在 AppDelegate.swift 加上一堆用來呼叫 Core Data 的程式
但是在 Xcode 8 之後,自動產生的程式使用了 NSPersistentContainer
要在 iOS 10 之後才能使用
如果要支援 iOS 8 的話,參考 StackOverflow
要在 AppDelegate.swift 加上舊版的程式(以下說明使用舊版程式)
假設一開始就沒有勾選「Use Core Data」的話
在 AppDelegate.swift 檔的前面加上
import CoreData
在 func ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 CoreData 儲存資料<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-24 Mon. 02:26:38</div><hr color="#008080" />───────────────────
要儲存資料,讓 APP 強制關掉再打開後資料還在的話
若資料只是單純的 key-value 值的話,可以使用 NSUserDefaults 就好
若是複雜的資料要用到資料庫的話,就要使用 Core Data 了
在 CoreData 底層是使用 SQLite 這個資料庫來儲存資料
只是把 SQL 的指令用物件導向包裝起來
<span style="color:#00F000">在專案加上 Core Data</span>
在開新專案時,如果有勾選「Use Core Data」
<div class="img" data-ori_w="714" data-ori_h="215" style="width:357px;height:108px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/JosE5lj.png" alt="[圖]" /></div>
會在檔案列表加上一個 XXXX.xcdatamodeld 的檔案
以及在 AppDelegate.swift 加上一堆用來呼叫 Core Data 的程式
但是在 Xcode 8 之後,自動產生的程式使用了 NSPersistentContainer
要在 iOS 10 之後才能使用
如果要支援 iOS 8 的話,參考 <a href="http://stackoverflow.com/a/39814217/5759096" target="_blank" rel="nofollow">StackOverflow</a>
要在 AppDelegate.swift 加上舊版的程式(以下說明使用舊版程式)
假設一開始就沒有勾選「Use Core Data」的話
在 AppDelegate.swift 檔的前面加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">CoreData</span>
</div>
在 func applicationWillTerminate(...){ ... } 之後加上
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Core Data stack</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">applicationDocumentsDirectory</span><span class="p">:</span> <span class="n">URL</span> <span class="p">=</span> <span class="p">{</span>
<span class="c1">// The directory the application uses to store the Core Data store file. </span>
<span class="kd">let</span> <span class="nv">urls</span> <span class="p">=</span> <span class="n">FileManager</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="n">urls</span><span class="p">(</span><span class="k">for</span><span class="p">:</span> <span class="p">.</span><span class="n">documentDirectory</span><span class="p">,</span> <span class="k">in</span><span class="p">:</span> <span class="p">.</span><span class="n">userDomainMask</span><span class="p">)</span>
<span class="k">return</span> <span class="n">urls</span><span class="p">[</span><span class="n">urls</span><span class="p">.</span><span class="bp">count</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">managedObjectModel</span><span class="p">:</span> <span class="bp">NSManagedObjectModel</span> <span class="p">=</span> <span class="p">{</span>
<span class="c1">// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.</span>
<span class="kd">let</span> <span class="nv">modelURL</span> <span class="p">=</span> <span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span> <span class="s">"CoreData"</span><span class="p">,</span> <span class="n">withExtension</span><span class="p">:</span> <span class="s">"momd"</span><span class="p">)</span><span class="o">!</span>
<span class="k">return</span> <span class="bp">NSManagedObjectModel</span><span class="p">(</span><span class="n">contentsOf</span><span class="p">:</span> <span class="n">modelURL</span><span class="p">)</span><span class="o">!</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">persistentStoreCoordinator</span><span class="p">:</span> <span class="bp">NSPersistentStoreCoordinator</span> <span class="p">=</span> <span class="p">{</span>
<span class="c1">// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.</span>
<span class="c1">// Create the coordinator and store</span>
<span class="kd">let</span> <span class="nv">coordinator</span> <span class="p">=</span> <span class="bp">NSPersistentStoreCoordinator</span><span class="p">(</span><span class="n">managedObjectModel</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">managedObjectModel</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">applicationDocumentsDirectory</span><span class="p">.</span><span class="n">appendingPathComponent</span><span class="p">(</span><span class="s">"SingleViewCoreData.sqlite"</span><span class="p">)</span>
<span class="kd">var</span> <span class="nv">failureReason</span> <span class="p">=</span> <span class="s">"There was an error creating or loading the application's saved data."</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">coordinator</span><span class="p">.</span><span class="n">addPersistentStore</span><span class="p">(</span><span class="n">ofType</span><span class="p">:</span> <span class="n">NSSQLiteStoreType</span><span class="p">,</span> <span class="n">configurationName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="n">at</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="c1">// Report any error we got.</span>
<span class="kd">var</span> <span class="nv">dict</span> <span class="p">=</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">AnyObject</span><span class="p">]()</span>
<span class="n">dict</span><span class="p">[</span><span class="n">NSLocalizedDescriptionKey</span><span class="p">]</span> <span class="p">=</span> <span class="s">"Failed to initialize the application's saved data"</span> <span class="k">as</span> <span class="nb">AnyObject</span><span class="p">?</span>
<span class="n">dict</span><span class="p">[</span><span class="n">NSLocalizedFailureReasonErrorKey</span><span class="p">]</span> <span class="p">=</span> <span class="n">failureReason</span> <span class="k">as</span> <span class="nb">AnyObject</span><span class="p">?</span>
<span class="n">dict</span><span class="p">[</span><span class="n">NSUnderlyingErrorKey</span><span class="p">]</span> <span class="p">=</span> <span class="n">error</span> <span class="k">as</span> <span class="bp">NSError</span>
<span class="kd">let</span> <span class="nv">wrappedError</span> <span class="p">=</span> <span class="bp">NSError</span><span class="p">(</span><span class="n">domain</span><span class="p">:</span> <span class="s">"YOUR_ERROR_DOMAIN"</span><span class="p">,</span> <span class="n">code</span><span class="p">:</span> <span class="mi">9999</span><span class="p">,</span> <span class="n">userInfo</span><span class="p">:</span> <span class="n">dict</span><span class="p">)</span>
<span class="c1">// Replace this with code to handle the error appropriately.</span>
<span class="c1">// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">"Unresolved error </span><span class="si">\(</span><span class="n">wrappedError</span><span class="si">)</span><span class="s">, </span><span class="si">\(</span><span class="n">wrappedError</span><span class="p">.</span><span class="n">userInfo</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="n">abort</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">coordinator</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="bp">NSManagedObjectContext</span> <span class="p">=</span> <span class="p">{</span>
<span class="c1">// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.</span>
<span class="kd">let</span> <span class="nv">coordinator</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">persistentStoreCoordinator</span>
<span class="kd">var</span> <span class="nv">managedObjectContext</span> <span class="p">=</span> <span class="bp">NSManagedObjectContext</span><span class="p">(</span><span class="n">concurrencyType</span><span class="p">:</span> <span class="p">.</span><span class="n">mainQueueConcurrencyType</span><span class="p">)</span>
<span class="n">managedObjectContext</span><span class="p">.</span><span class="n">persistentStoreCoordinator</span> <span class="p">=</span> <span class="n">coordinator</span>
<span class="k">return</span> <span class="n">managedObjectContext</span>
<span class="p">}()</span>
<span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Core Data Saving support</span>
<span class="kd">func</span> <span class="nf">saveContext</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">managedObjectContext</span><span class="p">.</span><span class="n">hasChanges</span> <span class="p">{</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">managedObjectContext</span><span class="p">.</span><span class="n">save</span><span class="p">()</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="c1">// Replace this implementation with code to handle the error appropriately.</span>
<span class="c1">// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.</span>
<span class="kd">let</span> <span class="nv">nserror</span> <span class="p">=</span> <span class="n">error</span> <span class="k">as</span> <span class="bp">NSError</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">"Unresolved error </span><span class="si">\(</span><span class="n">nserror</span><span class="si">)</span><span class="s">, </span><span class="si">\(</span><span class="n">nserror</span><span class="p">.</span><span class="n">userInfo</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="n">abort</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
其中 let modelURL = Bundle.main.url(forResource: "CoreData", withExtension: "momd")!
的 "CoreData" 要改成 XXXX.xcdatamodeld 的檔名 XXXX
新增一個 CoreData.xcdatamodeld 檔,點 command+n
<div class="img" data-ori_w="1129" data-ori_h="790" style="width:565px;height:395px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qP1nID1.png" alt="[圖]" /></div>
Save as: 輸入「CoreData」
<span style="color:#00F000">使用 Core Data 儲存資料至資料庫</span>
例如我們要加上看板瀏覽記錄的功能
打開檔案列表的 CoreData.xcdatamodeld
點「Add Entity」名稱改為「BoardHistory」
在 BoardHistory 中新增三個 Attributes:bi, name, title
三個 Sttributes 的 Type 分別為:Integer16, String, String
<div class="img" data-ori_w="960" data-ori_h="561" style="width:480px;height:281px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rOUSVqt.png" alt="[圖]" /></div>
在 Core Data 中,Entity 就相當於資料庫的資料表 Table
Attribute 就相當於資料庫的欄位 Field
在右邊的 Data Model 檢視器
<div class="img" data-ori_w="1222" data-ori_h="334" style="width:611px;height:167px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/O4IEr1e.png" alt="[圖]" /></div>
Codegen 預設為 Class Definition
會自動產生一個 BoardHistory 的類別
只要點上方選單的 Product / Clean 再點 Built 後就可以用了
但是在檔案列表不會出現
在進入看板的程式,新增成員函數 saveBoardHistory()
<div class="highlight"><span></span> <span class="c1">// 前面要加上 import CoreData</span>
<span class="kd">func</span> <span class="nf">saveBoardHistory</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">managedContext</span> <span class="p">=</span> <span class="p">(</span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">delegate</span> <span class="k">as</span><span class="p">!</span> <span class="n">AppDelegate</span><span class="p">).</span><span class="n">managedObjectContext</span>
<span class="kd">let</span> <span class="nv">entity</span> <span class="p">=</span> <span class="bp">NSEntityDescription</span><span class="p">.</span><span class="n">entity</span><span class="p">(</span><span class="n">forEntityName</span><span class="p">:</span> <span class="s">"BoardHistory"</span><span class="p">,</span> <span class="k">in</span><span class="p">:</span> <span class="n">managedContext</span><span class="p">)</span><span class="o">!</span>
<span class="c1">// 設定要新增的看板資料</span>
<span class="c1">// 使用自動產生的 BoardHistory 類別</span>
<span class="kd">let</span> <span class="nv">insBoard</span> <span class="p">=</span> <span class="n">BoardHistory</span><span class="p">(</span><span class="n">entity</span><span class="p">:</span> <span class="n">entity</span><span class="p">,</span> <span class="n">insertInto</span><span class="p">:</span> <span class="n">managedContext</span><span class="p">)</span>
<span class="n">insBoard</span><span class="p">.</span><span class="n">bi</span> <span class="p">=</span> <span class="nb">Int16</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">boardId</span><span class="p">)</span>
<span class="n">insBoard</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardName</span>
<span class="n">insBoard</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardTitle</span>
<span class="c1">// 將資料寫入資料庫</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">managedContext</span><span class="p">.</span><span class="n">save</span><span class="p">()</span>
<span class="p">}</span> <span class="k">catch</span> <span class="kd">let</span> <span class="nv">error</span> <span class="k">as</span> <span class="bp">NSError</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"Could not save. </span><span class="si">\(</span><span class="n">error</span><span class="si">)</span><span class="s">, </span><span class="si">\(</span><span class="n">error</span><span class="p">.</span><span class="n">userInfo</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
若成員變數 self.boardId 是存字串的話,存入資料庫前要先轉為 Int16
在程式取得 boardId, boardName, boardTitle 的地方,執行
<div class="highlight"><span></span> <span class="n">saveBoardHistory</span><span class="p">()</span>
</div>
<span style="color:#00F000">使用 Core Data 讀取資料庫</span>
在列出看板瀏覽記錄的程式
加上成員變數 boardHistoryList
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">boardHistoryList</span> <span class="p">=</span> <span class="p">[[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">String</span><span class="p">]]()</span>
</div>
先建立一個空的 [String: String] 陣列
加上成員函數 loadBoardHistory()
<div class="highlight"><span></span> <span class="c1">// 前面要加上 import CoreData</span>
<span class="kd">func</span> <span class="nf">loadBoardHistoryList</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">managedContext</span> <span class="p">=</span> <span class="p">(</span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">delegate</span> <span class="k">as</span><span class="p">!</span> <span class="n">AppDelegate</span><span class="p">).</span><span class="n">managedObjectContext</span>
<span class="kd">let</span> <span class="nv">fetchRequest</span> <span class="p">=</span> <span class="bp">NSFetchRequest</span><span class="p"><</span><span class="n">BoardHistory</span><span class="p">>(</span><span class="n">entityName</span><span class="p">:</span> <span class="s">"BoardHistory"</span><span class="p">)</span>
<span class="kd">var</span> <span class="nv">fetchResult</span> <span class="p">=</span> <span class="p">[</span><span class="n">BoardHistory</span><span class="p">]()</span>
<span class="k">do</span> <span class="p">{</span>
<span class="n">fetchResult</span> <span class="p">=</span> <span class="k">try</span> <span class="n">managedContext</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">fetchRequest</span><span class="p">).</span><span class="n">reversed</span><span class="p">()</span>
<span class="p">}</span> <span class="k">catch</span> <span class="kd">let</span> <span class="nv">error</span> <span class="k">as</span> <span class="bp">NSError</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"Could not fetch. </span><span class="si">\(</span><span class="n">error</span><span class="si">)</span><span class="s">, </span><span class="si">\(</span><span class="n">error</span><span class="p">.</span><span class="n">userInfo</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="kc">self</span><span class="p">.</span><span class="n">boardHistoryList</span><span class="p">.</span><span class="bp">removeAll</span><span class="p">()</span>
<span class="k">for</span> <span class="n">board</span> <span class="k">in</span> <span class="n">fetchResult</span> <span class="p">{</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">boardName</span> <span class="p">=</span> <span class="n">board</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="kd">let</span> <span class="nv">boardTitle</span> <span class="p">=</span> <span class="n">board</span><span class="p">.</span><span class="n">title</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">boardHistoryList</span><span class="p">.</span><span class="n">append</span><span class="p">([</span><span class="s">"name"</span><span class="p">:</span> <span class="n">boardName</span><span class="p">,</span> <span class="s">"title"</span><span class="p">:</span> <span class="n">boardTitle</span><span class="p">])</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
將資料庫中 BoardHistory 儲存的資料取出來
存在成員變數 boardHistoryList
取得 fetchResult 時,加上 reversed() 將順序反轉
讓新存進去的值會排在前面
不能直接將取得的 BoardHistory 陣列存在成員變數
因為 BoardHistory 的值可能會其他地方存取 CoreData 時被改掉
所以要將 BoardHistory 陣列裡的值取出來,
另外存成一個 [String: String] 陣列才行
在 viewDidLoad() 中加上
<div class="highlight"><span></span> <span class="n">loadBoardHistoryList</span><span class="p">()</span>
</div>
修改 Table view data source 函數
在 tableView(_:numberOfRowsInsection:) 設定列表有幾個 row
<div class="highlight"><span></span> <span class="k">return</span> <span class="n">boardHistoryList</span><span class="p">.</span><span class="bp">count</span>
</div>
在 tableView(_:cellForRowAt:) 設定要顯示的值
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">board</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardHistoryList</span><span class="p">[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">boardName</span> <span class="p">=</span> <span class="n">board</span><span class="p">[</span><span class="s">"name"</span><span class="p">]</span><span class="o">!</span>
<span class="kd">let</span> <span class="nv">boardTitle</span> <span class="p">=</span> <span class="n">board</span><span class="p">[</span><span class="s">"title"</span><span class="p">]</span><span class="o">!</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"</span><span class="si">\(</span><span class="n">boardName</span><span class="si">)</span><span class="s"> </span><span class="si">\(</span><span class="n">boardTitle</span><span class="si">)</span><span class="s">"</span>
</div>
<span style="color:#00F000">使用 Core Data 刪除資料庫某幾筆資料</span>
現在可以記錄並列出瀏覽過的看板了
但是若重覆進入同一個看板,瀏覽記錄就會顯示重覆的資料
所以要在加入新資料前,先把之前存過的記錄刪除
修改之前加上的 saveBoardHistory()
在 let insBoard = ... 的前面加上
<div class="highlight"><span></span> <span class="c1">//先刪除這個看板之前的瀏覽記錄</span>
<span class="kd">let</span> <span class="nv">fetchRequest</span> <span class="p">=</span> <span class="bp">NSFetchRequest</span><span class="p"><</span><span class="n">BoardHistory</span><span class="p">>(</span><span class="n">entityName</span><span class="p">:</span> <span class="s">"BoardHistory"</span><span class="p">)</span>
<span class="n">fetchRequest</span><span class="p">.</span><span class="n">predicate</span> <span class="p">=</span> <span class="bp">NSPredicate</span><span class="p">(</span><span class="n">formate</span><span class="p">:</span> <span class="s">"name == %@"</span><span class="p">,</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardName</span><span class="p">)</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">fetchResult</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">managedContext</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">fetchRequest</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="n">delBoard</span> <span class="k">in</span> <span class="n">fetchResult</span> <span class="p">{</span>
<span class="n">managedContext</span><span class="p">.</span><span class="n">delete</span><span class="p">(</span><span class="n">delBoard</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
使用 fetchRequest 的 predicate 過濾資料
例如我們要找出板名為 self.boardName 的資料
使用 if let fetchResult = try? managedContext.fetch(fetchRequest) {
確保有找到資料的話才執行刪除
for delBoard in fetchResult {
找到的資料可能有好幾筆,所以要用迴圈來刪除
managedContext.delete(delBoard)
呼叫 managedContext 的 delete(),傳入讀取出來的 BoardHistory 物件即可刪除
後面還要記得加上 managedContext.save() 才會儲存變更
參考
RayWenderlich <a href="https://www.raywenderlich.com/145809/getting-started-core-data-tutorial" target="_blank" rel="nofollow">Getting Started with Core Data Tutorial</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-24 02:26:38</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-07 20:31:59</span></div></pre>
Knuckles
[Xcode][Swift3] 加上搜尋輸入框 UISearchController
http://disp.cc/b/11-9Zvy
2017-04-21T00:52:07+08:00
2017-07-26T04:47:01+08:00
在右下方 Object library 裡有個 UISearchDisplayController
但那是比較舊的方法
從 iOS 8 開始,可以使用新的 UISearchController
但 UISearchController 沒有放在 Object library 裡
不能在 storyboard 中拉出來,要用程式加上去
例如我們想要加上一個搜尋看板的頁面
預設是先顯示瀏覽過的看板
在搜尋輸入框中輸入看板名稱後改成顯示搜尋的結果
像這樣
瀏覽過的看板的功能先空著之後再作
在輸入框輸入「Te」後在下方的 TableView 列出含有 te 的板名
新增一個類別程式檔 BoardSearchViewController.swift
Subclass of: UITableViewController
在 storyboard 新增一個 Table View Controller
自訂類別選擇「BoardSearchViewController」
Table View 的 Prototype Cells 數量設為「2」
兩個 Cell 的 Style 設為 Basic
Ident ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 加上搜尋輸入框 UISearchController<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-21 Fri. 00:51:16</div><hr color="#008080" />───────────────────
在右下方 Object library 裡有個 UISearchDisplayController
但那是比較舊的方法
從 iOS 8 開始,可以使用新的 UISearchController
但 UISearchController 沒有放在 Object library 裡
不能在 storyboard 中拉出來,要用程式加上去
例如我們想要加上一個搜尋看板的頁面
預設是先顯示瀏覽過的看板
在搜尋輸入框中輸入看板名稱後改成顯示搜尋的結果
像這樣
<div class="img" data-ori_w="1238" data-ori_h="457" style="width:619px;height:229px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ESyRHkY.png" alt="[圖]" /></div>
瀏覽過的看板的功能先空著之後再作
在輸入框輸入「Te」後在下方的 TableView 列出含有 te 的板名
新增一個類別程式檔 BoardSearchViewController.swift
Subclass of: UITableViewController
在 storyboard 新增一個 Table View Controller
自訂類別選擇「BoardSearchViewController」
Table View 的 Prototype Cells 數量設為「2」
兩個 Cell 的 Style 設為 Basic
Identifier 分別設為 BoardSearchHeaderCell, BoardSearchCell
第一個 BoardSearchHeaderCell 設定 Height: 20, BackgroundColor: #333333
裡面的 Label 設定 Color: Light Gray Color, Font: System 18.0
<div class="img" data-ori_w="1379" data-ori_h="354" style="width:690px;height:177px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/xh2zFAo.png" alt="[圖]" /></div>
修改 BoardSearchViewController.swift
新增成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">boardAllList</span> <span class="p">=</span> <span class="p">[</span><span class="nb">Any</span><span class="p">]()</span>
<span class="kd">var</span> <span class="nv">boardSearchResult</span> <span class="p">=</span> <span class="p">[</span><span class="nb">Any</span><span class="p">]()</span>
<span class="kd">var</span> <span class="nv">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">false</span>
<span class="kd">var</span> <span class="nv">searchController</span><span class="p">:</span> <span class="bp">UISearchController</span><span class="p">!</span>
</div>
boardAllList 用來儲存所有看板的列表,使用 [Any]() 代表先建立空的陣列
boardSearchResult 用來儲存搜尋的結果
shouldShowSearchResult 用來判斷是否要顯示搜尋結果
searchController 用來建立一個 UISearchController
從網路下載所有看板的列表
安裝 Alamofire 的方法參考這篇
<a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
先在前面加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">Alamofire</span>
</div>
新增成員函數 loadBoardAllList()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">loadBoardAllList</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">urlString</span> <span class="p">=</span> <span class="s">"https://disp.cc/api/get.php?act=bSearchList"</span>
<span class="n">Alamofire</span><span class="p">.</span><span class="n">request</span><span class="p">(</span><span class="n">urlString</span><span class="p">).</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">JSON</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">list</span> <span class="p">=</span> <span class="n">JSON</span><span class="p">[</span><span class="s">"list"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">Any</span><span class="p">]</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">boardAllList</span> <span class="p">=</span> <span class="n">list</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
用來下載所有看板的列表,然後存在成員變數 boardAllList
設定 UISearchController
繼承兩個類別 UISearchResultsUpdating, UISearchBarDelegate
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">BoardSearchViewController</span><span class="p">:</span> <span class="bp">UITableViewController</span><span class="p">,</span> <span class="bp">UISearchResultsUpdating</span><span class="p">,</span> <span class="bp">UISearchBarDelegate</span> <span class="p">{</span>
</div>
新增成員函數 initSearchController()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">initSearchController</span><span class="p">()</span> <span class="p">{</span>
<span class="n">searchController</span> <span class="p">=</span> <span class="bp">UISearchController</span><span class="p">(</span><span class="n">searchResultsController</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">searchResultsUpdater</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">dimsBackgroundDuringPresentation</span> <span class="p">=</span> <span class="kc">false</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">hidesNavigationBarDuringPresentation</span> <span class="p">=</span> <span class="kc">false</span>
<span class="kd">let</span> <span class="nv">searchBar</span> <span class="p">=</span> <span class="n">searchController</span><span class="p">.</span><span class="n">searchBar</span>
<span class="n">searchBar</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">searchBar</span><span class="p">.</span><span class="n">placeholder</span> <span class="p">=</span> <span class="s">"請輸入看板名稱"</span>
<span class="n">searchBar</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="s">"取消"</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span><span class="s">"_cancelButtonText"</span><span class="p">)</span>
<span class="n">searchBar</span><span class="p">.</span><span class="n">sizeToFit</span><span class="p">()</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">tableHeaderView</span> <span class="p">=</span> <span class="n">searchBar</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">backgroundView</span> <span class="p">=</span> <span class="bp">UIView</span><span class="p">()</span>
<span class="kc">self</span><span class="p">.</span><span class="n">definesPresentationContext</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
</div>
UISearchController(searchResultsController: nil)
建立 SearchController 時,傳入 nil 代表不另外開一個 TableView 來顯示
而是與本來的資料共用一個 TableView 來顯示
searchResultsUpdater = self
設定在這個類別使用 Updater 的代理函數
dimsBackgroundDuringPresentation = false
設定 false 代表不要在輸入搜尋框時,將下面的 TableView 變深色
hidesNavigationBarDuringPresentation = false
設定 false 代表不要在輸入搜尋框時,將搜尋框移到頁面最上方
searchBar.delegate = self
設定在這個類別使用 searchBar 的代理函數
searchBar.placeholder = "請輸入看板名稱"
輸入框中要顯示的輸入提示文字
searchBar.sizeToFit()
設定輸入框的寬度符合顯示的位置
tableView.tableHeaderView = searchBar
將輸入框放在 tableView 的 HeaderView
tableView.backgroundView = UIView
用來移除 tableView 的白色背景,避免下拉時顯示出來
self.definesPresentationContext = true
要設定一下 ViewController 的這個屬性
避免離開這個頁面時,搜尋輸入框沒有消失並蓋住了其他頁面
設定這個也會在離開頁面再回來時能保持搜尋狀態
(若搜尋頁是放在 ContainerView 中的子頁面,
那這行要寫在主頁面的 ViewDidLoad() 才行)
在 viewDidLoad() 執行上面兩個新增的成員函數
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewDidLoad</span><span class="p">()</span>
<span class="n">loadBoardAllList</span><span class="p">()</span>
<span class="n">initSearchController</span><span class="p">()</span>
<span class="p">}</span>
</div>
修改 TableView 的 data source 函數
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="n">tableview</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">shouldShowSearchResult</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">boardSearchResult</span><span class="p">.</span><span class="bp">count</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
第一個函數設定 section 數目為1
第二個函數設定 row 的數目
使用 shouldShowSearchResult 來決定是否要顯示搜尋結果
要顯示搜尋結果時,row 的數目設為陣列 boardSearchResult 的大小
新增兩個 tableView 的函數
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">heightForHeaderInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="n">CGFloat</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">20</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">viewForHeaderInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UIView</span><span class="p">?</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"BoardSearchHeaderCell"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">shouldShowSearchResult</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">?.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"搜尋結果"</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">?.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"瀏覽過的看板"</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
第一個函數設定 section 的 Header 顯示高度為 20
第二個函數設定 section 的 Header 使用之前在 storyboard 加上的 BoardSearchHeaderCell
使用 shouldShowSearchResult 判斷 Header 中要顯示什麼文字
設定每個 row 要顯示的內容
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"BoardSearchCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">shouldShowSearchResult</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">board</span> <span class="p">=</span> <span class="n">boardSearchResult</span><span class="p">[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span> <span class="k">as</span><span class="p">!</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="n">board</span><span class="p">[</span><span class="s">"name"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">""</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
加上 SearchBar 的代理函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - UISearchBarDelegte</span>
<span class="c1">// 輸入框取得focus時</span>
<span class="kd">func</span> <span class="nf">searchBarTextDidBeginEditing</span><span class="p">(</span><span class="kc">_</span> <span class="n">searchBar</span><span class="p">:</span> <span class="bp">UISearchBar</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="c1">// 輸入框失去focus時</span>
<span class="kd">func</span> <span class="nf">searchBarTextDidEndEditing</span><span class="p">(</span><span class="kc">_</span> <span class="n">searchBar</span><span class="p">:</span> <span class="bp">UISearchBar</span><span class="p">)</span> <span class="p">{</span>
<span class="n">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// 點擊輸入框的 Cancel 按鈕</span>
<span class="kd">func</span> <span class="nf">searchBarCancelButtonClicked</span><span class="p">(</span><span class="kc">_</span> <span class="n">searchBar</span><span class="p">:</span> <span class="bp">UISearchBar</span><span class="p">)</span> <span class="p">{</span>
<span class="n">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">false</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// 點擊鍵盤上的 Search 按鈕</span>
<span class="kd">func</span> <span class="nf">searchBarSearchButtonClicked</span><span class="p">(</span><span class="kc">_</span> <span class="n">searchBar</span><span class="p">:</span> <span class="bp">UISearchBar</span><span class="p">)</span> <span class="p">{</span>
<span class="n">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">searchBar</span><span class="p">.</span><span class="n">resignFirstResponder</span><span class="p">()</span>
<span class="p">}</span>
</div>
用來在輸入框輸入文字前後、點擊 Cancel 按鈕後、點擊鍵盤上的 Search 按鈕後,
要執行的動作
加上 UISearchResultsUpdating 的代理函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - UISearchResultsUpdating</span>
<span class="kd">func</span> <span class="nf">updateSearchResults</span><span class="p">(</span><span class="k">for</span> <span class="n">searchController</span><span class="p">:</span> <span class="bp">UISearchController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">searchString</span> <span class="p">=</span> <span class="n">searchController</span><span class="p">.</span><span class="n">searchBar</span><span class="p">.</span><span class="n">text</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="c1">// 輸入框沒有輸入文字時</span>
<span class="k">if</span> <span class="n">searchString</span><span class="p">.</span><span class="n">characters</span><span class="p">.</span><span class="bp">count</span> <span class="p">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">false</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">boardSearchResult</span> <span class="p">=</span> <span class="n">boardAllList</span><span class="p">.</span><span class="bp">filter</span><span class="p">({</span> <span class="n">obj</span> <span class="p">-></span> <span class="nb">Bool</span> <span class="k">in</span>
<span class="kd">let</span> <span class="nv">board</span> <span class="p">=</span> <span class="n">obj</span> <span class="k">as</span><span class="p">!</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">boardName</span> <span class="p">=</span> <span class="n">board</span><span class="p">[</span><span class="s">"name"</span><span class="p">]</span> <span class="k">as</span><span class="p">!</span> <span class="nb">String</span>
<span class="c1">// 只輸入一個字元時,只尋找板名開頭為這個字元的板</span>
<span class="k">if</span> <span class="n">searchString</span><span class="p">.</span><span class="n">characters</span><span class="p">.</span><span class="bp">count</span> <span class="p">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">boardName</span><span class="p">.</span><span class="n">lowercased</span><span class="p">().</span><span class="n">characters</span><span class="p">.</span><span class="bp">first</span> <span class="p">==</span> <span class="n">searchString</span><span class="p">.</span><span class="n">lowercased</span><span class="p">().</span><span class="n">characters</span><span class="p">.</span><span class="bp">first</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">boardName</span><span class="p">.</span><span class="n">range</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="n">searchString</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="bp">NSString</span><span class="p">.</span><span class="n">CompareOptions</span><span class="p">.</span><span class="n">caseInsensitive</span><span class="p">)</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="p">})</span>
<span class="n">shouldShowSearchResult</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
</div>
用來執行即時搜尋,每次輸入框中的文字改變時,就會執行這個函數
boardSearchResult = boardAllList.filter({ obj -> Bool in … })
使用陣列的 .filter() 將所有看板過濾為要尋找的看板,
然後存在另一個陣列 boardSearchResult
.filter() 的輸入參數為一個 callback function
陣列中的每個值會依序輸入這個 callback function
若 return true 則保留,return false 則去除
<span style="color:#00F000">使用程式將輸入框設為輸入狀態</span>
想要一進頁面,不用點輸入框,就直接跳出鍵盤進入輸入狀態的話
在 viewDidAppear() 加上
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewDidAppear</span><span class="p">(</span><span class="kc">_</span> <span class="n">animated</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">isActive</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="k">async</span><span class="p">(</span><span class="n">execute</span><span class="p">:</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">searchController</span><span class="p">.</span><span class="n">searchBar</span><span class="p">.</span><span class="n">becomeFirstResponder</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
</div>
想要點擊自訂的按鈕啟動輸入狀態的話
在新增的 @IBAction 加上
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">search</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">//要先捲動到最頂,避免 searchBar 位置錯誤</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">setContentOffset</span><span class="p">(</span><span class="n">CGPoint</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="o">-</span><span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">contentInset</span><span class="p">.</span><span class="n">top</span><span class="p">),</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="n">searchController</span><span class="p">.</span><span class="n">isActive</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">DispatchQueue</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="k">async</span><span class="p">(</span><span class="n">execute</span><span class="p">:</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">searchController</span><span class="p">.</span><span class="n">searchBar</span><span class="p">.</span><span class="n">becomeFirstResponder</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
</div>
<span style="color:#00F000">預設隱藏輸入框</span>
想要一進頁面時,輸入框是隱藏的,要往下拉才會顯示的話,
參考 <a href="https://stackoverflow.com/questions/32923091/show-uisearchcontroller-when-tableview-swipe-down" target="_blank" rel="nofollow">StackOverflow</a> 在 viewDidLoad() 加上
<div class="highlight"><span></span> <span class="n">tableView</span><span class="p">.</span><span class="n">contentOffset</span> <span class="p">=</span> <span class="n">CGPoint</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mf">44.0</span><span class="p">)</span>
</div>
<span style="color:#00F000">加上搜尋範圍的按鈕</span>
要在輸入框下方加上搜尋範圍的按鈕,例如可選擇要搜尋的是「標題」還是「作者」
在 initSearchController() 裡加上
<div class="highlight"><span></span> <span class="n">searchBar</span><span class="p">.</span><span class="n">scopeButtonTitles</span> <span class="p">=</span> <span class="p">[</span><span class="s">"標題"</span><span class="p">,</span> <span class="s">"作者"</span><span class="p">]</span>
</div>
在輸入狀態時就會變成像這樣
<div class="img" data-ori_w="524" data-ori_h="150" style="width:262px;height:75px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YlAfhYG.png" alt="[圖]" /></div>
加上點擊按鈕後會執行的代理函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">searchBar</span><span class="p">(</span><span class="kc">_</span> <span class="n">searchBar</span><span class="p">:</span> <span class="bp">UISearchBar</span><span class="p">,</span> <span class="n">selectedScopeButtonIndexDidChange</span> <span class="n">selectedScope</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 顯示點擊按鈕的名稱</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"click "</span> <span class="o">+</span> <span class="n">searchBar</span><span class="p">.</span><span class="n">scopeButtonTitles</span><span class="p">![</span><span class="n">selectedScope</span><span class="p">])</span>
<span class="k">if</span> <span class="n">selectedScope</span> <span class="p">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">// 點擊了"標題"時要做的事</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">selectedScope</span> <span class="p">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="c1">// 點擊了"作者"時要做的事</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
在點擊了「Search」按鈕的代理函數 searchBarSearchButtonClicked() 中
要取得搜尋範圍按鈕是選擇了哪一個,可使用
<div class="highlight"><span></span> <span class="k">if</span> <span class="n">searchBar</span><span class="p">.</span><span class="n">selectedScopeButtonIndex</span> <span class="p">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">//顯示搜尋"標題"的搜尋結果</span>
<span class="p">}</span>
</div>
□ 問題解決記錄
SearchBar 為輸入狀態時,位置莫名的下移了一段
解決方法參考 <a href="https://stackoverflow.com/questions/35093042/uisearchcontroller-search-bar-position-drops-64-points" target="_blank" rel="nofollow">StackOverflow</a>
在 storyboard 中,TableViewController 的屬性設定裡,將「Under Opaque Bars」勾選即可
搜尋框為輸入狀態時,捲動列表,再按搜尋框的取消時,出現 index out of range 的錯誤並閃退
但若是有先執行搜尋後,再捲動列表後按取消就沒事
解決方法,在 searchBarCancelButtonClicked() 的代理函數中
要先檢查是否有執行過搜尋,有的話才能執行 refresh 重整列表
參考
AppCoda <a href="http://www.appcoda.com.tw/custom-search-bar-tutorial/" target="_blank" rel="nofollow">如何利用UISearchController添加搜尋功能並打造客製化搜尋列</a>
RayWenderlich <a href="https://www.raywenderlich.com/113772/uisearchcontroller-tutorial" target="_blank" rel="nofollow">UISearchController Tutorial: Getting Started</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-21 00:51:16</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-07-26 04:47:01</span></div></pre>
Knuckles
[Xcode][Swift3] 在APP中用Safari開網頁 SFSafariViewController
http://disp.cc/b/11-9Zgi
2017-04-18T23:13:54+08:00
2017-04-19T00:48:25+08:00
如果想在 App 中顯示網頁,一般都是用 UIWebView
但 WebView 沒有網址列,還要自己加上重新整理、回上一下這些瀏覽按鈕才行
在 iOS9 之後,可以使用 SFSafariViewController
不用在 storyboard 中加上新的 ViewController
只要點了某個按鈕後,就會開啟一個新的頁面來顯示網頁
而且介面就和 Safari 一樣,有網址列與瀏覽按鈕
點了左上角的 Done 就會回到點擊按鈕頁面
可以安裝這個 App 來測試看看 SFSafariViewController
WebView - WKWebView and UIWebView rendering on the App Store
Read reviews, compare customer ratings, see screenshots, and learn more about WebView - WKWebView and UIWebView rendering. Download WebView - WKWebView and UIWebView rendering an ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 在APP中用Safari開網頁 SFSafariViewController<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-18 Tue. 23:13:54</div><hr color="#008080" />───────────────────
如果想在 App 中顯示網頁,一般都是用 UIWebView
但 WebView 沒有網址列,還要自己加上重新整理、回上一下這些瀏覽按鈕才行
在 iOS9 之後,可以使用 SFSafariViewController
不用在 storyboard 中加上新的 ViewController
只要點了某個按鈕後,就會開啟一個新的頁面來顯示網頁
而且介面就和 Safari 一樣,有網址列與瀏覽按鈕
點了左上角的 Done 就會回到點擊按鈕頁面
可以安裝這個 App 來測試看看 SFSafariViewController
<div style="background-color:#222; margin:.25em; padding:.25em; text-align:left;"><div class="quote_in"><a href="https://itunes.apple.com/app/id928647773" target="_blank" rel="nofollow"><strong>WebView - WKWebView and UIWebView rendering on the App Store</strong></a>
<a href="https://itunes.apple.com/app/id928647773" target="_blank" class="img" rel="nofollow"><div class="img" data-ori_w="175" data-ori_h="175" style="width:175px;height:175px"><img style="max-width:100%;" src="http://is5.mzstatic.com/image/thumb/Purple69/v4/67/4c/ba/674cba69-36bb-6a14-4a0c-7fdd3a668589/source/175x175bb.jpg" alt="[圖]" /></div></a>Read reviews, compare customer ratings, see screenshots, and learn more about WebView - WKWebView and UIWebView rendering. Download WebView - WKWebView and UIWebView rendering and enjoy it on your iPhone, iPad, and iPod touch.</div></div>
開一個 Single View Application 的新專案,
在 View Controller 上拉一個 Button
使用 Assistant Editor 建立點擊按鈕會執行的 @IBAction
名稱輸入 showSFSafari
修改 ViewController.swift
在 import UIKit 下一行加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">SafariServices</span>
</div>
類別加上繼承 SFSafariViewControllerDelegate
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">,</span> <span class="bp">SFSafariViewControllerDelegate</span> <span class="p">{</span>
</div>
修改剛剛加上的 @IBAction 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showSFSafari</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="s">"https://disp.cc/m/"</span><span class="p">)</span><span class="o">!</span>
<span class="k">if</span> <span class="cp">#available</span><span class="p">(</span><span class="cp">iOS</span> <span class="mf">9.0</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//確保是在 iOS9 之後的版本執行</span>
<span class="kd">let</span> <span class="nv">safariVC</span> <span class="p">=</span> <span class="cp">SFSafariViewController</span><span class="p">(</span><span class="cp">url</span><span class="p">:</span> <span class="cp">url</span><span class="p">,</span> <span class="cp">entersReaderIfAvailable</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="n">safariVC</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">safariVC</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// iOS 8 以下的話跳出 App 使用 Safari 開啟</span>
<span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">openURL</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
開啟 SFSafariViewController 的程式不能寫在 ViewDidLoad() 裡
要使用點擊 Button 開啟才行
執行後,點擊按鈕 SFSafari
<div class="img" data-ori_w="598" data-ori_h="1067" style="width:299px;height:534px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/MyeSeSR.png" alt="[圖]" /></div>
<div class="img imgur-gif" data-src="http://i.imgur.com/RbjLJZt.mp4" style="width:187px;height:333px;" title="點一下播放動畫"><img style="max-width:100%;" width="374" height="666" src="http://i.imgur.com/RbjLJZt.mp4" /><div class="play_btn"></div></div>
參考
<a href="https://code.tutsplus.com/tutorials/ios-9-getting-started-with-sfsafariviewcontroller--cms-24260" target="_blank" rel="nofollow">iOS 9: Getting Started With SFSafariViewController</a>
AppCoda <a href="http://www.appcoda.com.tw/search-api-sfsafariviewcontroller/" target="_blank" rel="nofollow">iOS 9 快速上手:搜尋 API 與 SFSafariViewController</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-18 23:13:54</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-19 00:48:24</span></div></pre>
Knuckles
[Xcode][Swift3] 文字上色 NSAttributedString
http://disp.cc/b/11-9Z7U
2017-04-17T19:07:41+08:00
2017-04-21T17:56:07+08:00
例如想要將標題文字:「123 標題文字」
前面的數字 123 顯示為深紅底黑色:「123 標題文字」
let numStr = "123"
let titleStr = "標題文字"
// 深紅色為 0x800000
let darkRed = UIColor(red: 0x80/255.0, green: 0, blue: 0, alpha: 1.0)
let numAttr = [NSForegroundColorAttributeName: UIColor.black,
NSBackgroundColorAttributeName: darkRed]
// 使用 attributes 陣列建立可擴充長度的 AttributedString
let attrStr = NSMutableAttributedString(string: numStr, attributes: numAttr)
// 後面接上一段 AttributedString
attrS ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 文字上色 NSAttributedString<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-17 Mon. 19:07:41</div><hr color="#008080" />───────────────────
例如想要將標題文字:「123 標題文字」
前面的數字 123 顯示為深紅底黑色:「<span class="fgB0 bgR">123</span> 標題文字」
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">numStr</span> <span class="p">=</span> <span class="s">"123"</span>
<span class="kd">let</span> <span class="nv">titleStr</span> <span class="p">=</span> <span class="s">"標題文字"</span>
<span class="c1">// 深紅色為 0x800000</span>
<span class="kd">let</span> <span class="nv">darkRed</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">(</span><span class="n">red</span><span class="p">:</span> <span class="mh">0x80</span><span class="o">/</span><span class="mf">255.0</span><span class="p">,</span> <span class="n">green</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">blue</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">numAttr</span> <span class="p">=</span> <span class="p">[</span><span class="n">NSForegroundColorAttributeName</span><span class="p">:</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">black</span><span class="p">,</span>
<span class="n">NSBackgroundColorAttributeName</span><span class="p">:</span> <span class="n">darkRed</span><span class="p">]</span>
<span class="c1">// 使用 attributes 陣列建立可擴充長度的 AttributedString</span>
<span class="kd">let</span> <span class="nv">attrStr</span> <span class="p">=</span> <span class="bp">NSMutableAttributedString</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">numStr</span><span class="p">,</span> <span class="n">attributes</span><span class="p">:</span> <span class="n">numAttr</span><span class="p">)</span>
<span class="c1">// 後面接上一段 AttributedString</span>
<span class="n">attrStr</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="bp">NSAttributedString</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="n">titleStr</span><span class="p">))</span>
<span class="c1">//將 Label 的文字設定為 attributedString</span>
<span class="n">titleLabel</span><span class="p">.</span><span class="n">attributedText</span> <span class="p">=</span> <span class="n">attrStr</span>
</div>
執行結果
<div class="img" data-ori_w="598" data-ori_h="639" style="width:299px;height:320px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/zPoZoWR.png" alt="[圖]" /></div>
參考
StackOverflow <a href="http://stackoverflow.com/a/32269975/5759096" target="_blank" rel="nofollow">How do I make an attributed string using Swift?</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-17 19:07:41</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-21 17:55:17</span></div></pre>
Knuckles
[Xcode][Swift3] TableView 使用 Section
http://disp.cc/b/11-9Z4E
2017-04-17T01:07:15+08:00
2017-04-17T01:07:15+08:00
TableView 如果要使用多組資料時
可以將資料分為不同的 section
每個 section 還可以在上下顯示 header 與 footer
例如先在 TableViewController 的成員變數加上三筆資料
let data1Array = ["data1-1"]
let data2Array = ["data2-1", "data2-2"]
let data3Array = ["data3-1", "data3-2", "data3-3"]
修改這兩個成員函數
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableVi ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] TableView 使用 Section<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-17 Mon. 01:07:14</div><hr color="#008080" />───────────────────
TableView 如果要使用多組資料時
可以將資料分為不同的 section
每個 section 還可以在上下顯示 header 與 footer
例如先在 TableViewController 的成員變數加上三筆資料
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">data1Array</span> <span class="p">=</span> <span class="p">[</span><span class="s">"data1-1"</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">data2Array</span> <span class="p">=</span> <span class="p">[</span><span class="s">"data2-1"</span><span class="p">,</span> <span class="s">"data2-2"</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">data3Array</span> <span class="p">=</span> <span class="p">[</span><span class="s">"data3-1"</span><span class="p">,</span> <span class="s">"data3-2"</span><span class="p">,</span> <span class="s">"data3-3"</span><span class="p">]</span>
</div>
修改這兩個成員函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Table view data source</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">3</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">switch</span><span class="p">(</span><span class="n">section</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span> <span class="n">data1Array</span><span class="p">.</span><span class="bp">count</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="k">return</span> <span class="n">data2Array</span><span class="p">.</span><span class="bp">count</span>
<span class="k">case</span> <span class="mi">2</span><span class="p">:</span> <span class="k">return</span> <span class="n">data3Array</span><span class="p">.</span><span class="bp">count</span>
<span class="k">default</span><span class="p">:</span> <span class="k">return</span> <span class="mi">0</span>
<span class="p">}</span>
</div>
第一個函數設定 TableView 有3個 section
第二個函數設定每個 section 各有幾個 row
修改顯示資料的成員函數 tableView(_:cellForRowAt:)
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"TableViewCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">section</span> <span class="p">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"row: </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s">, value: </span><span class="si">\(</span><span class="n">data1Array</span><span class="p">[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span><span class="si">)</span><span class="s">"</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">section</span> <span class="p">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"row: </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s">, value: </span><span class="si">\(</span><span class="n">data2Array</span><span class="p">[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span><span class="si">)</span><span class="s">"</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">section</span> <span class="p">==</span> <span class="mi">2</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"row: </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s">, value: </span><span class="si">\(</span><span class="n">data3Array</span><span class="p">[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span><span class="si">)</span><span class="s">"</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
執行到每個 section 時,row 的值又會從 0 開始
所以可以直接將 indexPath.row 的值設為每個資料陣列的索引值
執行結果:
<div class="img" data-ori_w="331" data-ori_h="424" style="width:166px;height:212px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/EtvsVI9.png" alt="[圖]" /></div>
要在每個 section 加上 header 的話
加上成員函數 tableView(_:titleForHeaderInSection:)
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">talbeView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">titleForHeaderInSectin</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">String</span><span class="p">?</span> <span class="p">{</span>
<span class="k">switch</span><span class="p">(</span><span class="n">section</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span> <span class="s">"data 1"</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="k">return</span> <span class="s">"data 2"</span>
<span class="k">case</span> <span class="mi">2</span><span class="p">:</span> <span class="k">return</span> <span class="s">"data 3"</span>
<span class="k">default</span><span class="p">:</span> <span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
設定每個 section 的 header 要顯示什麼文字
return nil 的話就不會顯示 header
執行結果
<div class="img" data-ori_w="345" data-ori_h="558" style="width:173px;height:279px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/xuUDKIT.png" alt="[圖]" /></div>
要調整每個 header 的高度的話,加上
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">talbeView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">heightForHeaderInSectin</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="n">CGFloat</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">20</span>
<span class="p">}</span>
</div>
將每個 section 的 header 高度都設為 20
執行結果
<div class="img" data-ori_w="394" data-ori_h="519" style="width:197px;height:260px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gFUo2mJ.png" alt="[圖]" /></div>
要自訂 header 樣式的話
到 storyboard
在 TableView 的屬性檢視器設定 Prototype Cells 的數量為 2
在第一個 cell,設定 Identifier:「HeaderCell」, Background:「Dark Gray Color」
設定高度為 20, Label 的文字顏色設定為「White」,像這樣
<div class="img" data-ori_w="888" data-ori_h="244" style="width:444px;height:122px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/5eVHgBb.png" alt="[圖]" /></div>
刪除之前加上的成員函數 tableView(_:titleForHeaderInSection:)
改為
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">viewForHeaderInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UIView</span><span class="p">?</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"HeaderCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="k">switch</span><span class="p">(</span><span class="n">section</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="n">cell</span><span class="p">?.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"data 1"</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="n">cell</span><span class="p">?.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"data 2"</span>
<span class="k">case</span> <span class="mi">2</span><span class="p">:</span> <span class="n">cell</span><span class="p">?.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"data 3"</span>
<span class="k">default</span><span class="p">:</span> <span class="k">break</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
執行結果
<div class="img" data-ori_w="377" data-ori_h="520" style="width:189px;height:260px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Vyma9aR.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-17 01:07:14</span>
文章作者可對每則推文做回應...
</div></pre>
Knuckles
[Xcode][Swift3] TableView 加上"載入更多"按鈕
http://disp.cc/b/11-9YHe
2017-04-13T22:45:23+08:00
2017-04-13T23:00:59+08:00
依照這篇 [Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS
的方法,再加一個看板列表頁
在看板列表的 TableView 中,預設載入了 20 筆資料
當滑動到底時,想要顯示一個「載入更多」的按鈕
點了以後會再多載入 20 筆資料
在 TableView 設定 Prototype Cells 的數量為 2
第二個 Cell 的 Identifier 輸入「BoardListMoreCell」
將 Style 改為「Basic」,將 Label 的文字改為「點此載入更多」
自訂類別使用預設的 UITableViewCell
修改看板列表的類別程式檔
用來存列表資料的陣列設定為
var boardListArray:[Any] = []
一開始先設為空的陣列
加上成員變數
var numPageLoad: Int = 0
記錄已載入了幾頁
修改從網路載入資料的成員函數 loadData()
將 numPageLoad 加入網址參數,用來讀取要載入哪一頁的資料
將讀取到的資料使用 append 的方 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] TableView 加上"載入更多"按鈕<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-13 Thu. 22:44:39</div><hr color="#008080" />───────────────────
依照這篇 <a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
的方法,再加一個看板列表頁
在看板列表的 TableView 中,預設載入了 20 筆資料
當滑動到底時,想要顯示一個「載入更多」的按鈕
點了以後會再多載入 20 筆資料
在 TableView 設定 Prototype Cells 的數量為 2
<div class="img" data-ori_w="1278" data-ori_h="274" style="width:639px;height:137px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6P7NHGh.png" alt="[圖]" /></div>
第二個 Cell 的 Identifier 輸入「BoardListMoreCell」
<div class="img" data-ori_w="1281" data-ori_h="322" style="width:641px;height:161px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/TGq6QCa.png" alt="[圖]" /></div>
將 Style 改為「Basic」,將 Label 的文字改為「點此載入更多」
自訂類別使用預設的 UITableViewCell
修改看板列表的類別程式檔
用來存列表資料的陣列設定為
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">boardListArray</span><span class="p">:[</span><span class="nb">Any</span><span class="p">]</span> <span class="p">=</span> <span class="p">[]</span>
</div>
一開始先設為空的陣列
加上成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">numPageLoad</span><span class="p">:</span> <span class="nb">Int</span> <span class="p">=</span> <span class="mi">0</span>
</div>
記錄已載入了幾頁
修改從網路載入資料的成員函數 loadData()
將 numPageLoad 加入網址參數,用來讀取要載入哪一頁的資料
將讀取到的資料使用 append 的方式加入 boardListArray
然後將 numPageLoad 加 1,呼叫 TableView 重載資料
例如
<div class="highlight"><span></span> <span class="k">if</span> <span class="kd">let</span> <span class="nv">blist</span> <span class="p">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"blist"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">Any</span><span class="p">]</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">boardListArray</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">contentsOf</span><span class="p">:</span> <span class="n">blist</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">numPageLoad</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
</div>
修改 tableView(_:numberOfRowsInSection:) 的內容為
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">num</span><span class="p">:</span> <span class="nb">Int</span> <span class="p">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardListArray</span><span class="p">.</span><span class="bp">count</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">num</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardListArray</span><span class="p">.</span><span class="bp">count</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">num</span>
</div>
當 boardListArray 有值時,要多顯示一列,用來當載入更多的按鈕
修改 tableView(_:cellForRowAt:) 的內容為
<div class="highlight"><span></span> <span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="o"><</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardListArray</span><span class="p">.</span><span class="bp">count</span> <span class="p">{</span>
<span class="c1">// 原本的內容</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"BoardListMoreCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="c1">// 可以在這修改按鈕的顯示的文字</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"已載入 </span><span class="si">\(</span><span class="kc">self</span><span class="p">.</span><span class="n">numPageLoad</span><span class="si">)</span><span class="s"> 頁,點此載入更多"</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
加上成員函數 tableView(_:didSelectRowAt:)
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">didSelectRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="p">.</span><span class="n">row</span> <span class="p">==</span> <span class="kc">self</span><span class="p">.</span><span class="n">boardListArray</span><span class="p">.</span><span class="bp">count</span> <span class="p">{</span>
<span class="n">loadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
設定點擊「載入更多」那一列時,執行 loadData()
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-13 22:44:39</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-13 23:00:15</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 PageViewController 滑動換頁
http://disp.cc/b/11-9XZn
2017-04-07T01:37:10+08:00
2017-04-09T19:46:25+08:00
延續上一篇 [Xcode][Swift3] 使用 ContainerView 切換子頁面 - KnucklesNote板 - Disp BBS
我們可以使用上方的選單切換下方要載入的子頁面了
如果想要使用左右滑動換頁的話
那就要再加上 Page View Controller
將原本連結 Page1ViewController 的 Segue 刪除
拉一個 Page View Controller 進來
按著 Ctrl 將 Container View 拉至 Page View Controller
跳出的選單選「Embed」
點一下 Segue 在屬性檢視器
輸入 Identifier 為「ContainerViewSegue」
按 Command+n 新增 Cocoa Touch Class
類別名稱輸入「PageViewController」
Subclass of:「UIPageViewController」
設定 Page View Controller 的自訂類別為「PageViewController」
在 Page View Controller 的屬性檢視器設定換頁 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 PageViewController 滑動換頁<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-07 Fri. 01:36:58</div><hr color="#008080" />───────────────────
延續上一篇 <a href="http://disp.cc/b/11-9XMd" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 ContainerView 切換子頁面 - KnucklesNote板 - Disp BBS</a>
我們可以使用上方的選單切換下方要載入的子頁面了
如果想要使用左右滑動換頁的話
那就要再加上 Page View Controller
將原本連結 Page1ViewController 的 Segue 刪除
拉一個 Page View Controller 進來
<div class="img" data-ori_w="1472" data-ori_h="518" style="width:736px;height:259px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/JZN5V3r.png" alt="[圖]" /></div>
按著 Ctrl 將 Container View 拉至 Page View Controller
跳出的選單選「Embed」
<div class="img" data-ori_w="672" data-ori_h="454" style="width:336px;height:227px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/H3PrGpQ.png" alt="[圖]" /></div>
點一下 Segue 在屬性檢視器
輸入 Identifier 為「ContainerViewSegue」
<div class="img" data-ori_w="1012" data-ori_h="393" style="width:506px;height:197px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YwyWKyX.png" alt="[圖]" /></div>
按 Command+n 新增 Cocoa Touch Class
類別名稱輸入「PageViewController」
Subclass of:「UIPageViewController」
<div class="img" data-ori_w="705" data-ori_h="220" style="width:353px;height:110px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/jA7NOSf.png" alt="[圖]" /></div>
設定 Page View Controller 的自訂類別為「PageViewController」
<div class="img" data-ori_w="882" data-ori_h="397" style="width:441px;height:199px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ojBxn50.png" alt="[圖]" /></div>
在 Page View Controller 的屬性檢視器設定換頁類型為「Scroll」滑動換頁
<div class="img" data-ori_w="820" data-ori_h="467" style="width:410px;height:234px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rxD7WXv.png" alt="[圖]" /></div>
修改剛剛新增的 PageViewController.swift
新增成員變數
<div class="highlight"><span></span> <span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page1ViewController</span><span class="p">:</span> <span class="n">Page1ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page1"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page1ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page2ViewController</span><span class="p">:</span> <span class="n">Page2ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page2"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page2ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page3ViewController</span><span class="p">:</span> <span class="n">Page3ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page3"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page3ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page4ViewController</span><span class="p">:</span> <span class="n">Page4ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page4"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page4ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page5ViewController</span><span class="p">:</span> <span class="n">Page5ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page5"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page5ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">orderedViewControllers</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIViewController</span><span class="p">]</span> <span class="p">=</span> <span class="p">{</span>
<span class="p">[</span><span class="kc">self</span><span class="p">.</span><span class="n">page1ViewController</span><span class="p">,</span> <span class="kc">self</span><span class="p">.</span><span class="n">page2ViewController</span><span class="p">,</span> <span class="kc">self</span><span class="p">.</span><span class="n">page3ViewController</span><span class="p">,</span> <span class="kc">self</span><span class="p">.</span><span class="n">page4ViewController</span><span class="p">,</span> <span class="kc">self</span><span class="p">.</span><span class="n">page5ViewController</span><span class="p">]</span>
<span class="p">}()</span>
</div>
就是把本來寫在 ViewController 中取得五個子頁面的程式改寫在這邊
然後再把這五個子頁面存成一個 UIViewController 陣列 orderedViewControllers
將類別加上繼承 UIPageViewControllerDataSource
修改 class PageViewController: UIPageViewController { 這行為
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">PageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="bp">UIPageViewControllerDataSource</span> <span class="p">{</span>
</div>
在 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">dataSource</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">setViewControllers</span><span class="p">([</span><span class="n">page1ViewController</span><span class="p">],</span> <span class="n">direction</span><span class="p">:</span> <span class="p">.</span><span class="n">forward</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
</div>
使用 setViewControllers() 指定 page1ViewController 為預設要顯示的 Controller
第一個參數要傳入一個 UIViewController 陣列
是用在像是 eBook 的 App,一頁有兩個子頁面的情況
如果一頁只有一個子頁面的話,就傳入一個只有一個值的陣列即可
加上兩個 UIPageViewControllerDataSource 的成員函數
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - UIPageViewControllerDataSource</span>
<span class="kd">func</span> <span class="nf">pageViewController</span><span class="p">(</span><span class="kc">_</span> <span class="n">pageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="n">viewControllerBefore</span> <span class="n">viewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UIViewController</span><span class="p">?</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">index</span> <span class="p">=</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="n">viewController</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nv">previousIndex</span> <span class="p">=</span> <span class="n">index</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">guard</span> <span class="n">previousIndex</span> <span class="o">>=</span><span class="mi">0</span> <span class="o">&&</span> <span class="n">previousIndex</span> <span class="o"><</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="bp">count</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">orderedViewControllers</span><span class="p">[</span><span class="n">previousIndex</span><span class="p">]</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">pageViewController</span><span class="p">(</span><span class="kc">_</span> <span class="n">pageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="n">viewControllerAfter</span> <span class="n">viewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UIViewController</span><span class="p">?</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">index</span> <span class="p">=</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="n">viewController</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nv">nextIndex</span> <span class="p">=</span> <span class="n">index</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">guard</span> <span class="n">nextIndex</span> <span class="o">>=</span><span class="mi">0</span> <span class="o">&&</span> <span class="n">nextIndex</span> <span class="o"><</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="bp">count</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">orderedViewControllers</span><span class="p">[</span><span class="n">nextIndex</span><span class="p">]</span>
<span class="p">}</span>
</div>
第一個函數是在往左滑動時,輸入參數為目前的 ViewController
我們要提供左邊的 ViewController 是哪一個
利用之前存的 ViewController 陣列 orderedViewControllers
取得目前的 ViewController 在陣列中的 index
將 index - 1 後,就可以取得左邊的 ViewController
第二個函數就是要取得右邊的 ViewController 為何
再來修改主頁面的程式檔 ViewController.swift
因為換頁的動作改到 PageViewController 去執行了
所以可以將之前寫的換頁程式都刪除,或是註解掉
將這幾個成員函數 mainStoryboard、page1ViewController ~ page5ViewController、
selectedViewcontroller 刪除
將成員函數 changePage() 與 prepare() 刪除
將 viewDidLoad() 中的 selectedViewController = page1ViewController 這行刪除
在五個按鈕的 @IBAction 中,將 changePage(to: ...) 刪除
執行看看
<div class="img" data-ori_w="525" data-ori_h="561" style="width:263px;height:281px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hX7bZYg.png" alt="[圖]" /></div>
可以左右滑動換頁了,只是上方的選單不能點,也不會跟著改變選取狀態
<span style="color:#00F000">點擊選單讓 PageViewController 換頁</span>
在要主頁面點擊選單,讓子頁面的 PageViewController 執行換頁的話
要使用 Segue 取得子頁面的 PageViewController
然後執行 PageViewController 的 setViewControllers()
先在 PageViewcontroller.swift 加上成員函數 showPage()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">showPage</span><span class="p">(</span><span class="n">byIndex</span> <span class="n">index</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">viewController</span> <span class="p">=</span> <span class="n">orderedViewControllers</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
<span class="n">setViewControllers</span><span class="p">([</span><span class="n">viewController</span><span class="p">],</span> <span class="n">direction</span><span class="p">:</span> <span class="p">.</span><span class="n">forward</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
使用子頁面陣列的索引值取得要顯示哪個子頁面後
執行 setViewControllers() 來換頁
修改主頁面的程式檔 ViewController.swift
加上成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">pageViewController</span><span class="p">:</span> <span class="n">PageViewController</span><span class="p">!</span>
</div>
加上成員函數
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">prepare</span><span class="p">(</span><span class="k">for</span> <span class="n">segue</span><span class="p">:</span> <span class="bp">UIStoryboardSegue</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">segue</span><span class="p">.</span><span class="n">identifier</span> <span class="p">==</span> <span class="s">"ContainerViewSegue"</span> <span class="p">{</span>
<span class="n">pageViewController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">!</span> <span class="n">PageViewController</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
使用 Segue 取得 Container View 內嵌的 PageViewController
現在我們可以在主頁面 ViewController
執行 PageViewController 的成員函數 showPage() 來換頁了
修改五個按鈕的 @IBAction 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage1</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page1Button</span><span class="p">)</span>
<span class="n">pageViewController</span><span class="p">.</span><span class="n">showPage</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage2</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page2Button</span><span class="p">)</span>
<span class="n">pageViewController</span><span class="p">.</span><span class="n">showPage</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage3</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page3Button</span><span class="p">)</span>
<span class="n">pageViewController</span><span class="p">.</span><span class="n">showPage</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage4</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page4Button</span><span class="p">)</span>
<span class="n">pageViewController</span><span class="p">.</span><span class="n">showPage</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="mi">3</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage5</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page5Button</span><span class="p">)</span>
<span class="n">pageViewController</span><span class="p">.</span><span class="n">showPage</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="mi">4</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行看看
<div class="img" data-ori_w="1096" data-ori_h="371" style="width:548px;height:186px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/BgrnGYG.png" alt="[圖]" /></div>
可以點選單換頁了,而且換頁後也可以用左右滑動換頁
但是滑動換頁後,選單的選取狀態沒有改變
<span style="color:#00F000">滑動換頁後改變選單的選取狀態</span>
要在滑動換頁後改變選單的選取狀態
首先要能在 PageViewController 中取得主頁面的 ViewController
修改 PageViewController.swift
新增成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">mainViewController</span><span class="p">:</span> <span class="n">ViewController</span><span class="p">!</span>
</div>
修改 ViewController.swift
在成員函數 prepare() 中
pageViewController = segue.destination ... 的下一行加上
<div class="highlight"><span></span> <span class="n">pageViewController</span><span class="p">.</span><span class="n">mainViewController</span> <span class="p">=</span> <span class="kc">self</span>
</div>
在主頁面使用 Segue 取得子頁面位址的時候
也將主頁面的位址記錄在子頁面的成員變數
在主頁面 ViewController.swift 新增成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">changeTab</span><span class="p">(</span><span class="n">byIndex</span> <span class="n">index</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">index</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page1Button</span><span class="p">)</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page2Button</span><span class="p">)</span>
<span class="k">case</span> <span class="mi">2</span><span class="p">:</span> <span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page3Button</span><span class="p">)</span>
<span class="k">case</span> <span class="mi">3</span><span class="p">:</span> <span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page4Button</span><span class="p">)</span>
<span class="k">case</span> <span class="mi">4</span><span class="p">:</span> <span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page5Button</span><span class="p">)</span>
<span class="k">default</span><span class="p">:</span> <span class="k">return</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
使用陣列的索引值呼叫之前寫的 changeTab(to:) 來改變按鈕選取的狀態
現在我們可以在 PageViewController 中使用
mainViewController.changeTab(byIndex:)
來改變頁籤的選取狀態了
要能在左右滑動換頁後,執行換頁籤的動作
要加上 UIPageViewController 的 Delegate 函數
修改 PageViewController.swift
將 class PageViewController: UIPageViewController ... 這行改為
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">PageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="bp">UIPageViewControllerDataSource</span><span class="p">,</span> <span class="bp">UIPageViewControllerDelegate</span> <span class="p">{</span>
</div>
加上繼承 UIPageViewControllerDelegate
加上一個成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">willTransitionTo</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">!</span>
</div>
用來記錄將要移至的子頁面
在成員函數 viewDidLoad() 中加上
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
</div>
加上兩個 Delegate 函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">pageViewController</span><span class="p">(</span><span class="kc">_</span> <span class="n">pageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="n">willTransitionTo</span> <span class="n">pendingViewControllers</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIViewController</span><span class="p">])</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">willTransitionTo</span> <span class="p">=</span> <span class="n">pendingViewController</span><span class="p">.</span><span class="bp">first</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">pageViewController</span><span class="p">(</span><span class="kc">_</span> <span class="n">pageViewController</span><span class="p">:</span> <span class="bp">UIPageViewController</span><span class="p">,</span> <span class="n">didFinishAnimation</span> <span class="n">finished</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">,</span> <span class="n">previousVieControllers</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIViewController</span><span class="p">],</span> <span class="n">transitionCompleted</span> <span class="n">completed</span><span class="p">:</span> <span class="nb">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">completed</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">index</span> <span class="p">=</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">willTransitionTo</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="kd">let</span> <span class="nv">previousViewController</span> <span class="p">=</span> <span class="n">previousViewControllers</span><span class="p">.</span><span class="bp">first</span><span class="p">!</span>
<span class="kd">let</span> <span class="nv">previousIndex</span> <span class="p">=</span> <span class="n">orderedViewControllers</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="n">previousViewController</span><span class="p">)</span>
<span class="k">if</span> <span class="n">index</span> <span class="o">!=</span> <span class="n">previousIndex</span> <span class="p">{</span>
<span class="n">mainViewController</span><span class="p">.</span><span class="n">changeTab</span><span class="p">(</span><span class="n">byIndex</span><span class="p">:</span> <span class="n">index</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
第一個函數會在左右滑動換頁剛開始時執行
這邊可以取得目標的子頁面為何,將他存在成員變數 willTransitionTo
第二個函數會在左右滑動換頁結束時執行
參數 completed 代表是否有完成換頁的動作
有的話再利用成員變數 willTransitionTo 記錄的目標子頁面
取得子頁面在陣列 orderedViewControllers 的索引值 index
然後執行 mainViewController.changeTab(byIndex: index) 切換頁籤狀態
若是滑到下一頁時,又快速滑回上一頁,這樣 willTransitionTo 只會取到下一頁
所以要加上 if index != previousIndex 避免移動到錯誤的頁籤
執行結果
<div class="img imgur-gif" data-src="http://i.imgur.com/NV8JcoR.mp4" style="width:187px;height:163px;" title="點一下播放動畫"><img style="max-width:100%;" width="374" height="326" src="http://i.imgur.com/NV8JcoR.mp4" /><div class="play_btn"></div></div>
程式碼已上傳至
<a href="https://github.com/KnucklesHuang/Swift-HorizontalScrollBar" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/Swift-HorizontalScrollBar</a>
參考
<a href="https://spin.atomicobject.com/2015/12/23/swift-uipageviewcontroller-tutorial/" target="_blank" rel="nofollow">How to Use UIPageViewController in Swift</a>
StackOverflow <a href="http://stackoverflow.com/questions/34348275/pass-data-between-viewcontroller-and-containerviewcontroller/34349405#34349405" target="_blank" rel="nofollow">Pass data between ViewController and ContainerViewController</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-07 01:36:58</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-09 19:45:44</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 ContainerView 切換子頁面
http://disp.cc/b/11-9XMd
2017-04-04T23:45:23+08:00
2017-04-09T01:14:38+08:00
依照前一篇 [Xcode][Swift3] 使用 ScrollView 建立水平捲動選單 - KnucklesNote板 - Disp BBS
使用水平選單產生選取頁籤的效果
但下方只有一個 TableView,只能簡單的更換不同的資料來顯示
如果想在下方建立一個子頁面,可以切換成不同的 Controller 的話
要使用 Container View
在 storyboard 使用 Container View 載入預設的第一頁
刪掉原本的 TableView 元件,拉一個 Container View 進來
可以看到 Container View 旁邊會附帶一個相同大小的 View Controller
對 Container View 加上四個方向為0的 Constraints
接下來可以在附帶的 View Controller 中加上 TableView 元件
或是改成使用 Table View Controller
要改用 Table View Controller 的話
先刪掉附帶的 View Controller,拉一個 Table View Controller 進來
...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 ContainerView 切換子頁面<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-04 Tue. 23:45:11</div><hr color="#008080" />───────────────────
依照前一篇 <a href="http://disp.cc/b/11-9XsS" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 ScrollView 建立水平捲動選單 - KnucklesNote板 - Disp BBS</a>
使用水平選單產生選取頁籤的效果
但下方只有一個 TableView,只能簡單的更換不同的資料來顯示
如果想在下方建立一個子頁面,可以切換成不同的 Controller 的話
要使用 Container View
<span style="color:#00F000">在 storyboard 使用 Container View 載入預設的第一頁</span>
刪掉原本的 TableView 元件,拉一個 Container View 進來
<div class="img" data-ori_w="1045" data-ori_h="607" style="width:523px;height:304px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ZiIMpK4.png" alt="[圖]" /></div>
可以看到 Container View 旁邊會附帶一個相同大小的 View Controller
對 Container View 加上四個方向為0的 Constraints
<div class="img" data-ori_w="779" data-ori_h="658" style="width:390px;height:329px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/f1uDodW.png" alt="[圖]" /></div>
接下來可以在附帶的 View Controller 中加上 TableView 元件
或是改成使用 Table View Controller
要改用 Table View Controller 的話
先刪掉附帶的 View Controller,拉一個 Table View Controller 進來
<div class="img" data-ori_w="1181" data-ori_h="600" style="width:591px;height:300px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gU9Bdaw.png" alt="[圖]" /></div>
按著 Ctrl 將 Container View 拉至 Table View Controller
跳出的選單選擇「Embed」
<div class="img" data-ori_w="755" data-ori_h="626" style="width:378px;height:313px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nXlRyQR.png" alt="[圖]" /></div>
這樣即可將 Container View 改為附帶一個 Table View Controller
點一下連接的 Segue,在屬性檢視器輸入 Identifier 為「ContainerViewSegue」
之後在程式會用到
<div class="img" data-ori_w="1248" data-ori_h="822" style="width:624px;height:411px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/4Hk5zAt.png" alt="[圖]" /></div>
新增一個 Table View Controller 的程式檔
點 command+n 新增一個 Cocoa Touch Class
Class 名稱輸入「Page1ViewController」
Subclass of 「UITableViewController」
<div class="img" data-ori_w="709" data-ori_h="211" style="width:355px;height:106px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/v4H4J9U.png" alt="[圖]" /></div>
新增好程式檔後,要到 storyboard 設定自訂類別為「Page1Viewcontroller」
然後在 Storyboard ID 輸入「Page1」,之後在程式會用到
<div class="img" data-ori_w="906" data-ori_h="326" style="width:453px;height:163px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/dn5wvY5.png" alt="[圖]" /></div>
設定 Table View Cell 的 Identifier 為「TableViewCell」
<div class="img" data-ori_w="1158" data-ori_h="326" style="width:579px;height:163px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6Kl2sDA.png" alt="[圖]" /></div>
有使用下拉更新的話,在 Table View Controller 的屬性檢視器
開啟並設定下拉更新的功能
<div class="img" data-ori_w="794" data-ori_h="605" style="width:397px;height:303px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/g7hss8S.png" alt="[圖]" /></div>
編輯程式檔 Page1ViewController.swift
將原本寫在 ViewController.swift 中關於 TableView 的程式改寫在這裡
像這樣
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">Page1ViewController</span><span class="p">:</span> <span class="bp">UITableViewController</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">loadData</span><span class="p">()</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"load data"</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">?.</span><span class="n">isRefreshing</span><span class="p">)</span><span class="o">!</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">?.</span><span class="n">endRefreshing</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">viewDidLoad</span><span class="p">()</span>
<span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">?.</span><span class="n">addTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">loadData</span><span class="p">),</span> <span class="k">for</span><span class="p">:</span> <span class="n">UIControlEvents</span><span class="p">.</span><span class="n">valueChanged</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">didReceiveMemoryWarning</span><span class="p">()</span> <span class="p">{</span>
<span class="kc">super</span><span class="p">.</span><span class="n">didReceiveMemoryWarning</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Table view data source</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">20</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"TableViewCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"測試標題 </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s">"</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
注意 Table View Controller 已有內建 refreshControl
所以不用在成員變數新增,只要在 viewDidLoad() 設定要執行的函數即可
原本寫在 ViewController.swift 中關於 TableView 的程式就可以刪除了
執行看看
<div class="img" data-ori_w="524" data-ori_h="638" style="width:262px;height:319px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rirZ8Ao.png" alt="[圖]" /></div>
<span style="color:#00F000">使用程式載入其他頁</span>
在 storyboard 中,使用 Container View 只能內嵌一個 View Controller 而已
要切換為內嵌其他的 View Controller 的話只能用程式達成
假設其他四頁都只要使用簡單的 View Controller 就好
新增四個繼承 ViewController 的類別程式檔,檔名分別為
Page2ViewController.swift
Page3ViewController.swift
Page4ViewController.swift
Page5ViewController.swift
在 storyboard 拉四個 View Controller 進來
調整 Background 為不同的顏色以示區別
<div class="img" data-ori_w="1242" data-ori_h="762" style="width:621px;height:381px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/p0wvYm3.png" alt="[圖]" /></div>
此時會出現一個警告訊息,說這幾個 View Controller 沒有進入點
只要有設定 Storyboard ID 就可以消除這個警告
設定每頁的自訂類別,以及 Storyboard ID
例如第5頁的自訂類別為 Page5ViewController,Storyboard ID 為 Page5
<div class="img" data-ori_w="1238" data-ori_h="760" style="width:619px;height:380px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/9fnRw28.png" alt="[圖]" /></div>
再來要寫程式來切換要顯示的頁面
先使用 Assistant edior 新增 Contain View 的 @IBOutlet
名稱輸入「containerView」
<div class="img" data-ori_w="1278" data-ori_h="548" style="width:639px;height:274px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/8uDlMEj.png" alt="[圖]" /></div>
編輯程式檔 ViewController.swift
新增成員變數
<div class="highlight"><span></span> <span class="c1">// 1.</span>
<span class="kd">var</span> <span class="nv">page1ViewController</span><span class="p">:</span> <span class="n">Page1ViewController</span><span class="p">!</span>
<span class="c1">// 2.</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page2ViewController</span><span class="p">:</span> <span class="n">Page2ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page2"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page2ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page3ViewController</span><span class="p">:</span> <span class="n">Page3ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page3"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page3ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page4ViewController</span><span class="p">:</span> <span class="n">Page4ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page4"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page4ViewController</span>
<span class="p">}()</span>
<span class="kr">lazy</span> <span class="kd">var</span> <span class="nv">page5ViewController</span><span class="p">:</span> <span class="n">Page5ViewController</span> <span class="p">=</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">storyboard</span><span class="p">!.</span><span class="n">instantiateViewController</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"Page5"</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page5ViewController</span>
<span class="p">}()</span>
<span class="c1">// 3.</span>
<span class="kd">var</span> <span class="nv">selectedViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">!</span>
</div>
1. page1ViewController 因為有使用 Segue 連結了,後面會使用 prepare() 來取得
2. 其他四頁使用 Storyboard ID 來取得頁面的 Controller,存為成員變數
前面加 lazy 代表變數第一次使用到的時候才會去取得初始值
後面接一個立即執行的匿名函數,因為內容只有一行所以可以不用加 return
3. 使用成員變數 selectedViewController 來記錄目前選取的是哪一頁
新增成員函數 prepare(for:sender:)
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">prepare</span><span class="p">(</span><span class="k">for</span> <span class="n">segue</span><span class="p">:</span> <span class="bp">UIStoryboardSegue</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">segue</span><span class="p">.</span><span class="n">identifier</span> <span class="p">==</span> <span class="s">"ContainerViewSegue"</span> <span class="p">{</span>
<span class="n">page1ViewController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">!</span> <span class="n">Page1ViewController</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
使用連結兩頁面的 Segue 來取得 page1ViewController
在 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="n">selectedViewController</span> <span class="p">=</span> <span class="n">page1ViewController</span>
</div>
設定預設選取的是第一頁
新增成員函數 changePage(to:)
<div class="highlight"><span></span> <span class="c1">// 1.</span>
<span class="kd">func</span> <span class="nf">changePage</span><span class="p">(</span><span class="n">to</span> <span class="n">newViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 2. Remove previous viewController</span>
<span class="n">selectedViewController</span><span class="p">.</span><span class="n">willMove</span><span class="p">(</span><span class="n">toParentViewcontroller</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="n">selectedViewController</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">removeFromSuperview</span><span class="p">()</span>
<span class="n">selectedViewController</span><span class="p">.</span><span class="n">removeFromParentViewController</span><span class="p">()</span>
<span class="c1">// 3. Add new viewController</span>
<span class="n">addChildViewController</span><span class="p">(</span><span class="n">newViewController</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">containerView</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">newViewController</span><span class="p">.</span><span class="n">view</span><span class="p">)</span>
<span class="n">newViewController</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">frame</span> <span class="p">=</span> <span class="n">containerView</span><span class="p">.</span><span class="n">bounds</span>
<span class="n">newViewController</span><span class="p">.</span><span class="n">didMove</span><span class="p">(</span><span class="n">toParentViewController</span><span class="p">:</span> <span class="kc">self</span><span class="p">)</span>
<span class="c1">// 4.</span>
<span class="kc">self</span><span class="p">.</span><span class="n">selectedViewController</span> <span class="p">=</span> <span class="n">newViewController</span>
<span class="p">}</span>
</div>
1. 參數傳入新頁面的 Controller,會先移除 Container View 上附帶的舊 Controller
再將新的 Controller 加到 Container View
2. 移除 Controller 前要先呼叫 willMove() 讓 Contrller 執行離開頁面的程式
然後使用 removeFromSuperview() 將 Controller 移出 Container View
最後使用 removeFromParentViewController 將 Contrller 移出主頁的 Controller
3. 先用 addChildViewController() 將新的 Controller 加進主頁的 Controller
接著將新的 Controller 加進 Container View
設定新 Controller 中頁面的尺寸為 Container View 的大小
呼叫 didMove() 讓新的 Controller 執行頁面載入的程式
4. 修改目前選取的頁面為新的 Controller
修改五個按鈕的 @IBAction 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage1</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page1Button</span><span class="p">)</span>
<span class="n">changePage</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page1ViewController</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage2</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page2Button</span><span class="p">)</span>
<span class="n">changePage</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page2ViewController</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage3</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page3Button</span><span class="p">)</span>
<span class="n">changePage</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page3ViewController</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage4</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page4Button</span><span class="p">)</span>
<span class="n">changePage</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page4ViewController</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage5</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page5Button</span><span class="p">)</span>
<span class="n">changePage</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page5ViewController</span><span class="p">)</span>
<span class="p">}</span>
</div>
在每個按鈕點擊後執行 changePage(to:)
執行看看
<div class="img" data-ori_w="1382" data-ori_h="890" style="width:691px;height:445px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/sgeE6VR.png" alt="[圖]" /></div>
參考
Cocoacasts <a href="https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/" target="_blank" rel="nofollow">Managing View CodePathControllers With Container View Controllers</a>
CodePath <a href="https://github.com/codepath/ios_guides/wiki/Container-View-Controllers-Quickstart" target="_blank" rel="nofollow">Container View Controllers Quickstart</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-04 23:45:11</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-09 01:13:58</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 ScrollView 建立水平捲動選單
http://disp.cc/b/11-9XsS
2017-04-01T23:58:22+08:00
2017-04-25T00:18:08+08:00
e作者: Knuckles (站長 那克斯) 看板: KnucklesNote
延續上一篇 [Xcode][Swift3] 手動加入 TableView 元件 - KnucklesNote板 - Disp BBS
在 ViewController 手動加入 TableView
以及上方固定的 ScrollView 後
這篇要在 ScrollView 中加上可水平捲動的選單
先在 ViewController 的屬性檢視器,
取消勾選「Adjust Scroll View Insets」
避免 ScrollView 中的元件自動下移 NavigationBar 的高度
我們預計在水平選單中放5個 90x36 的按鈕
所以要先放一個 450x36 的 View
拉一個 View 放到 ScrollView 裡面
在 View 加上 上下左右為 0 的 Constraints
以及 寬 450、高 36 的 Constraints
Update Frames 選擇「Items of New Constraints」
點「Add 6 Constraints」
ScrollView 中的 Vie ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;">e作者: Knuckles (站長 那克斯) 看板: KnucklesNote
標題: [Xcode][Swift3] 使用 ScrollView 建立水平捲動選單
時間: 2017-04-01 Sat. 23:58:12
───────────────────
延續上一篇 <a href="http://disp.cc/b/11-9XrN" target="_blank" rel="nofollow">[Xcode][Swift3] 手動加入 TableView 元件 - KnucklesNote板 - Disp BBS</a>
在 ViewController 手動加入 TableView
以及上方固定的 ScrollView 後
這篇要在 ScrollView 中加上可水平捲動的選單
先在 ViewController 的屬性檢視器,
取消勾選「Adjust Scroll View Insets」
<div class="img" data-ori_w="1219" data-ori_h="554" style="width:610px;height:277px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/TKPu0Wi.png" alt="[圖]" /></div>
避免 ScrollView 中的元件自動下移 NavigationBar 的高度
我們預計在水平選單中放5個 90x36 的按鈕
所以要先放一個 450x36 的 View
拉一個 View 放到 ScrollView 裡面
<div class="img" data-ori_w="893" data-ori_h="573" style="width:447px;height:287px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/b5zOou5.png" alt="[圖]" /></div>
在 View 加上 上下左右為 0 的 Constraints
以及 寬 450、高 36 的 Constraints
Update Frames 選擇「Items of New Constraints」
點「Add 6 Constraints」
<div class="img" data-ori_w="1022" data-ori_h="714" style="width:511px;height:357px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Jqhi6yA.png" alt="[圖]" /></div>
ScrollView 中的 View 必需設定四個方向與寬高共六個 Constraints 才行
拉一個按鈕進來,在尺寸檢視器設定 X:0, Y:0, Width:90, Height:36
顯示文字改為「按鈕1」
<div class="img" data-ori_w="1285" data-ori_h="558" style="width:643px;height:279px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/eka4qO1.png" alt="[圖]" /></div>
點一下按鈕1後,按 command+c 複製,再按 command+p 四次貼上四個按鈕
修改四個按鈕的 x 分別為 90, 180, 270, 360
顯示文字分別為「按鈕2」「按鈕3」「按鈕4」「按鈕5」
<div class="img" data-ori_w="1286" data-ori_h="462" style="width:643px;height:231px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/k2J6ETd.png" alt="[圖]" /></div>
執行看看,水平選單可以向左滑動以顯示按鈕5
<div class="img" data-ori_w="525" data-ori_h="259" style="width:263px;height:130px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/RaVYzqn.png" alt="[圖]" /></div>
<span style="color:#00F000">點選按鈕後改變按鈕顏色</span>
想要讓按鈕像頁籤一樣可以選取,點了以後會改變顏色的話
先修改按鈕1為預設先選取的顏色
Text Color: Black Color, Background: Light Gray Color
<div class="img" data-ori_w="892" data-ori_h="339" style="width:446px;height:170px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/OHpGe3i.png" alt="[圖]" /></div>
每個按鈕的字體都調成 System 18.0
使用 Assistant editor 建立5個按鈕的 @IBOutlet
<div class="img" data-ori_w="1431" data-ori_h="678" style="width:716px;height:339px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/n481iIp.png" alt="[圖]" /></div>
使用 Assistant editor 建立5個按鈕的 @IBAction
<div class="img" data-ori_w="1435" data-ori_h="762" style="width:718px;height:381px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/R38i3DG.png" alt="[圖]" /></div>
切換回 Standard editor
在 ViewController.swift 加上成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">selectedButton</span><span class="p">:</span> <span class="bp">UIButton</span><span class="p">!</span>
</div>
用來記錄現在選取的是哪一個按鈕
在 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="n">selectedButton</span> <span class="p">=</span> <span class="n">page1Button</span>
</div>
設定預設先選取的是按鈕1
新增一個成員函數 changeTab(to:)
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">changeTab</span><span class="p">(</span><span class="n">to</span> <span class="n">newButton</span><span class="p">:</span> <span class="bp">UIButton</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 先利用 tintColor 取得 Button 預設的文字顏色</span>
<span class="kd">let</span> <span class="nv">defaultColor</span> <span class="p">=</span> <span class="n">selectedButton</span><span class="p">.</span><span class="n">tintColor</span>
<span class="c1">// 將目前選取的按鈕改成未選取的顏色</span>
<span class="n">selectedButton</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">white</span>
<span class="n">selectedButton</span><span class="p">.</span><span class="n">setTitleColor</span><span class="p">(</span><span class="n">defaultColor</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="p">.</span><span class="n">normal</span><span class="p">)</span>
<span class="c1">// 將參數傳來的新按鈕改成選取的顏色</span>
<span class="n">newButton</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">lightGray</span>
<span class="n">newButton</span><span class="p">.</span><span class="n">setTitleColor</span><span class="p">(</span><span class="bp">UIColor</span><span class="p">.</span><span class="n">black</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="p">.</span><span class="n">normal</span><span class="p">)</span>
<span class="c1">// 將目前選取的按鈕改為新的按鈕</span>
<span class="n">selectedButton</span> <span class="p">=</span> <span class="n">newButton</span>
<span class="p">}</span>
</div>
修改5個按鈕的 @IBAction 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage1</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page1Button</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage2</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page2Button</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage3</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page3Button</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage4</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page4Button</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">showPage5</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">changeTab</span><span class="p">(</span><span class="n">to</span><span class="p">:</span> <span class="n">page5Button</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行看看,點選按鈕後會改變選取的按鈕了
<div class="img" data-ori_w="1637" data-ori_h="144" style="width:819px;height:72px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rTak3Id.png" alt="[圖]" /></div>
<span style="color:#00F000">水平選單下方加上分隔線</span>
將下方的 tableView 調低一點,拉一個 View 進來
<div class="img" data-ori_w="937" data-ori_h="634" style="width:469px;height:317px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qRTdTN3.png" alt="[圖]" /></div>
點 Add New Constraints
設定上左右為 0,高為 1,取消勾選「Constrain to margins」
Update Frames 選 Items of New Constraints
點 Add 4 Constraints
<div class="img" data-ori_w="670" data-ori_h="670" style="width:335px;height:335px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/0e1AApM.png" alt="[圖]" /></div>
點一下 View 後按 Enter,將名稱改為「Separator View」
<div class="img" data-ori_w="932" data-ori_h="325" style="width:466px;height:163px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/QFsXLAx.png" alt="[圖]" /></div>
重新設定下方 TableView 的 Constraints
點 TableView 後點「Resolve Auto Layout Issues」/「Clear Constraints」
Add Constraints 時上方邊界改為與 Separator View 的間距為 0
<div class="img" data-ori_w="862" data-ori_h="719" style="width:431px;height:360px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Ew5m0Kx.png" alt="[圖]" /></div>
將 Separator View 的背景色改為 Light Gray Color
<div class="img" data-ori_w="1294" data-ori_h="380" style="width:647px;height:190px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/8I9VXIm.png" alt="[圖]" /></div>
執行結果
<div class="img" data-ori_w="1078" data-ori_h="148" style="width:539px;height:74px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/jqWwqsX.png" alt="[圖]" /></div>
<span style="color:#00F000">點擊 Status Bar 捲動 TableView 至最上方</span>
在一般的頁面往下捲動後,只要點擊上方的 Status Bar 時,就會捲動到最上方
但加了水平捲動選單後,點 Status Bar
下方的 TableView 不會捲動到最上方
參考 <a href="http://stackoverflow.com/questions/7165913/scroll-to-top-of-uitableview-by-tapping-status-bar" target="_blank" rel="nofollow">StackOverflow</a>
要將水平選單中的 ScrollView 拉一個 @IBOutlet 後
在 viewDidLoad() 中設定 ScrollView 的屬性 .scrollToTop = false
這樣點擊 Status Bar 時,下方的 TableView 就會捲動到最上方了
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-01 23:58:12</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-25 00:18:08</span></div></pre>
Knuckles
[Xcode][Swift3] 手動加入 TableView 元件
http://disp.cc/b/11-9XrN
2017-04-01T18:35:41+08:00
2017-04-01T18:35:41+08:00
依照這篇 [Xcode][Swift3] 使用 TableView 產生列表頁 - KnucklesNote板 - Disp BBS
使用 TableViewController 建立一個列表頁後
若是想在列表上方,加上其他不會隨列表捲動的元件,例如 ScrollView
會發現沒有辦法,ScrollView 只能放在 TableView 裡面
會隨著列表一起捲動
要讓 ScrollView 固定在上方不會被捲動的話
要使用 ViewController 後再手動加入 TableView 元件
重新建立一個專案試試看
一開始使用「Single View Application」產生的專案
預設就有一個 ViewController 了
點選 ViewController 後,點「Editor」/「Embed In」/「Navigation Controller」
就可以幫 ViewController 加上 Navigation Controller
拉一個我們想要固定在上方的 ScrollView
在 ScrollView 加上 上、左、右為 0 以及高度為 36 的 Constra ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 手動加入 TableView 元件<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-04-01 Sat. 18:35:31</div><hr color="#008080" />───────────────────
依照這篇 <a href="http://disp.cc/b/11-9UkW" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 TableView 產生列表頁 - KnucklesNote板 - Disp BBS</a>
使用 TableViewController 建立一個列表頁後
若是想在列表上方,加上其他不會隨列表捲動的元件,例如 ScrollView
<div class="img" data-ori_w="1288" data-ori_h="397" style="width:644px;height:199px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ZNNy31L.png" alt="[圖]" /></div>
會發現沒有辦法,ScrollView 只能放在 TableView 裡面
會隨著列表一起捲動
<div class="img" data-ori_w="1107" data-ori_h="569" style="width:554px;height:285px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/IdBguFV.png" alt="[圖]" /></div>
要讓 ScrollView 固定在上方不會被捲動的話
要使用 ViewController 後再手動加入 TableView 元件
重新建立一個專案試試看
一開始使用「Single View Application」產生的專案
預設就有一個 ViewController 了
點選 ViewController 後,點「Editor」/「Embed In」/「Navigation Controller」
<div class="img" data-ori_w="987" data-ori_h="609" style="width:494px;height:305px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/IQcxL2g.png" alt="[圖]" /></div>
就可以幫 ViewController 加上 Navigation Controller
拉一個我們想要固定在上方的 ScrollView
<div class="img" data-ori_w="1278" data-ori_h="368" style="width:639px;height:184px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/3dCuvWG.png" alt="[圖]" /></div>
在 ScrollView 加上 上、左、右為 0 以及高度為 36 的 Constraints
不要勾選 Constrain to margins,Update Frames 選「Items of New Constraints」
<div class="img" data-ori_w="416" data-ori_h="650" style="width:208px;height:325px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/IX0ymMy.png" alt="[圖]" /></div>
拉一個 TableView 進來
<div class="img" data-ori_w="830" data-ori_h="629" style="width:415px;height:315px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/VNpq3sz.png" alt="[圖]" /></div>
在 TableView 加上 上下左右為 0 的 Constraints
注意上方是要與 ScrollView 的距離為 0
不要勾 Constrain to margins,Update Frames 選「Items of New Constraints」
<div class="img" data-ori_w="415" data-ori_h="642" style="width:208px;height:321px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/v3evXtt.png" alt="[圖]" /></div>
在 TableView 的屬性檢視器,修改 Prototype Cells 的數目為 1
<div class="img" data-ori_w="1224" data-ori_h="469" style="width:612px;height:235px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/X8mi0Vj.png" alt="[圖]" /></div>
在 TableViewCell 的屬性檢視器,輸入 Identifier 為「TableViewCell」
<div class="img" data-ori_w="1222" data-ori_h="495" style="width:611px;height:248px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/0nAlWpH.png" alt="[圖]" /></div>
使用 Assistant editor 按著 Ctrl 將 TableView 拉到程式碼中
名稱輸入 tableView
<div class="img" data-ori_w="1206" data-ori_h="622" style="width:603px;height:311px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/v31otbT.png" alt="[圖]" /></div>
產生一個連結 TableView 的成員函數 tableView
<div class="highlight"><span></span> <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">!</span>
</div>
切回 Standard editor,編輯程式檔 ViewController.swift
將 class ViewController: UIViewConstroller { 這行改為
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">ViewController</span><span class="p">:</span> <span class="n">UIViewConstroller</span><span class="p">,</span> <span class="bp">UITableViewDataSource</span><span class="p">,</span> <span class="bp">UITableViewDelegate</span> <span class="p">{</span>
</div>
讓類別繼承 UITableViewDataSource 與 UITableViewDelegate
修改成員函數 viewDidLoad(),加上
<div class="highlight"><span></span> <span class="n">tableView</span><span class="p">.</span><span class="n">dataSource</span> <span class="p">=</span> <span class="kc">self</span>
<span class="n">tableView</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
</div>
加上三個 TableView 的 delegate 成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">)</span> <span class="p">-></span><span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">20</span>
<span class="p">}</span>
<span class="kd">internal</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"TableViewCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"測試標題 </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s">"</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
執行看看
<div class="img" data-ori_w="524" data-ori_h="360" style="width:262px;height:180px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yL8i3PM.png" alt="[圖]" /></div>
捲動時上方的 ScrollView 可以固定住了
<span style="color:#00F000">列表上方加上分隔線</span>
想要在列表上方也顯示一條分隔線的話
在成員函數 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="c1">// 上方加上分隔線</span>
<span class="kd">let</span> <span class="nv">frame</span> <span class="p">=</span> <span class="n">CGRect</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">width</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="mi">1</span> <span class="o">/</span> <span class="bp">UIScreen</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">scale</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">line</span><span class="p">:</span> <span class="bp">UIView</span> <span class="p">=</span> <span class="bp">UIView</span><span class="p">(</span><span class="n">frame</span><span class="p">:</span> <span class="n">frame</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">tableHeaderView</span> <span class="p">=</span> <span class="n">line</span>
<span class="n">line</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">separatorColor</span>
</div>
執行結果
<div class="img" data-ori_w="524" data-ori_h="332" style="width:262px;height:166px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Qzobzua.png" alt="[圖]" /></div>
<span style="color:#00F000">加上下拉更新</span>
使用 TableViewController 時,有內建 refreshControl
手動加上 TableView 的話就沒有了,不過可以自己加上去
修改 ViewController.swift
新增成員變數 refreshControl
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">refreshControl</span> <span class="p">=</span> <span class="bp">UIRefreshControl</span><span class="p">()</span>
</div>
新增成員函數 loadData()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">loadData</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">//開始載入資料...</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"load data"</span><span class="p">)</span>
<span class="c1">// 載入完資料後...</span>
<span class="k">if</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">.</span><span class="n">isRefreshing</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">.</span><span class="n">endRefreshing</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
這邊省略從網路下載資料的程式,只有在 Console 顯示 "load data"
當載入完資料後要將 refreshControl 關閉
在成員函數 viewDidLoad() 中加上
<div class="highlight"><span></span> <span class="n">refreshControl</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">clear</span>
<span class="n">refreshControl</span><span class="p">.</span><span class="n">tintColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">black</span>
<span class="n">refreshControl</span><span class="p">.</span><span class="n">attributedTitle</span> <span class="p">=</span> <span class="bp">NSAttributedString</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="s">"更新資料"</span><span class="p">)</span>
<span class="n">refreshControl</span><span class="p">.</span><span class="n">addTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">loadData</span><span class="p">),</span> <span class="k">for</span><span class="p">:</span> <span class="n">UIControlEvents</span><span class="p">.</span><span class="n">valueChanged</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">refreshControl</span><span class="p">)</span>
</div>
執行結果
<div class="img" data-ori_w="524" data-ori_h="491" style="width:262px;height:246px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/lkIQdLl.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-04-01 18:35:31</span></div></pre>
Knuckles
[Xcode][Swift3] 顯示提示對話框 UIAlertController
http://disp.cc/b/11-9WTa
2017-03-26T23:43:44+08:00
2017-05-17T00:35:44+08:00
要跳出一個訊息,讓使用者必需點選「確定」或「取消」才能繼續時
可以使用 UIAlertController
只有一個確定鈕
例如想要在點選某個按鈕時,出現簡單的提示訊息
在 storyboard 加上一個 UIButton
點著 Ctrl 拉到 assistant editor 建立一個 @IBAction
將建立的函數改為
@IBAction func alert(_ sender: Any) {
let alert = UIAlertController(title: "標題文字", message: "要顯示的訊息", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "確定", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
執行結果,點擊按鈕後,頁面中央會顯示 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 顯示提示對話框 UIAlertController<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-26 Sun. 23:43:37</div><hr color="#008080" />───────────────────
要跳出一個訊息,讓使用者必需點選「確定」或「取消」才能繼續時
可以使用 UIAlertController
<span style="color:#00F000">只有一個確定鈕</span>
例如想要在點選某個按鈕時,出現簡單的提示訊息
在 storyboard 加上一個 UIButton
點著 Ctrl 拉到 assistant editor 建立一個 @IBAction
<div class="img" data-ori_w="837" data-ori_h="361" style="width:419px;height:181px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LeN7d1h.png" alt="[圖]" /></div>
將建立的函數改為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">alert</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="bp">UIAlertController</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"標題文字"</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">"要顯示的訊息"</span><span class="p">,</span> <span class="n">preferredStyle</span><span class="p">:</span> <span class="p">.</span><span class="n">alert</span><span class="p">)</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"確定"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行結果,點擊按鈕後,頁面中央會顯示
<div class="img" data-ori_w="472" data-ori_h="236" style="width:236px;height:118px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/5iCTtqi.png" alt="[圖]" /></div>
若是將 preferredStyle: 的 .alert 改成 .actionSheet,
會變成在頁面底部顯示
<div class="img" data-ori_w="1287" data-ori_h="1059" style="width:644px;height:530px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oBCxpxZ.png" alt="[圖]" /></div>
關於輸入參數 preferredStyle:
.alert 是 enum 變數 UIAlertControllerStyle.alert 的簡寫
enum 是一種將相關的數值集合起來的變數,宣告的型式為
<div class="highlight"><span></span><span class="kd">enum</span> <span class="nc">UIAlertControllerStyle</span> <span class="p">:</span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">actionSheet</span>
<span class="k">case</span> <span class="n">alert</span>
<span class="p">}</span>
</div>
注意若 preferredStyle 是使用 .actionSheet 時,要再加上
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">button</span> <span class="p">=</span> <span class="n">sender</span> <span class="k">as</span><span class="p">!</span> <span class="bp">UIView</span>
<span class="n">alert</span><span class="p">.</span><span class="n">popoverPresentationController</span><span class="p">?.</span><span class="n">sourceView</span> <span class="p">=</span> <span class="n">button</span>
<span class="n">alert</span><span class="p">.</span><span class="n">popoverPresentationController</span><span class="p">?.</span><span class="n">sourceRect</span> <span class="p">=</span> <span class="n">button</span><span class="p">.</span><span class="n">bounds</span>
</div>
不然在 iPad 執行會閃退
在 iPad 使用 .actionSheet 會在按鈕下方跳出 popover 方塊
<div class="img" data-ori_w="804" data-ori_h="314" style="width:402px;height:157px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/NWDO2ej.png" alt="[圖]" /></div>
如果按鈕是使用 Navigation Bar 上的 BarButtonItem 的話,要改成加上
<div class="highlight"><span></span> <span class="n">alert</span><span class="p">.</span><span class="n">popoverPresentationController</span><span class="p">?.</span><span class="n">barButtonItem</span> <span class="p">=</span> <span class="n">sender</span> <span class="k">as</span><span class="p">?</span> <span class="bp">UIBarButtonItem</span>
</div>
在 iPad 點擊 BarButtonItem 跳出的 .actionSheet 會像這樣
<div class="img" data-ori_w="803" data-ori_h="279" style="width:402px;height:140px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/5b3vwL2.png" alt="[圖]" /></div>
<span style="color:#00F000">有確定和取消兩個按鈕</span>
點取消就直接關閉對話框,點確定後要能執行後續的動作
將函數 alert(_:) 的內容改為
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="bp">UIAlertController</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"標題文字"</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">"要顯示的訊息"</span><span class="p">,</span> <span class="n">preferredStyle</span><span class="p">:</span> <span class="p">.</span><span class="n">alert</span><span class="p">)</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"取消"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="n">cancel</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"確定"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">action</span> <span class="k">in</span>
<span class="c1">//點了確定後要做的事</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"點選了確定按鈕"</span><span class="p">)</span>
<span class="p">}))</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
</div>
要先放取消按鈕,style: 要使用 .cancel
確定按鈕的 handler: 加入一個 callback 匿名函數 { action in /* 要做的事 */ }
執行結果
<div class="img" data-ori_w="464" data-ori_h="231" style="width:232px;height:116px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/i6c50Mg.png" alt="[圖]" /></div>
點了確定後會在 Console 視窗顯示「點選了確定按鈕」
按鈕的 style: 也可以使用 .destructive 代表是個危險的動作
按鈕文字會變成紅色,像這樣
<div class="img" data-ori_w="467" data-ori_h="231" style="width:234px;height:116px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/QY2Iy5j.png" alt="[圖]" /></div>
<span style="color:#00F000">使用三個按鈕</span>
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="bp">UIAlertController</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"標題文字"</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">"要顯示的訊息"</span><span class="p">,</span> <span class="n">preferredStyle</span><span class="p">:</span> <span class="p">.</span><span class="n">alert</span><span class="p">)</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"取消"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="n">cancel</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"確定"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"略過"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
</div>
執行結果
<div class="img" data-ori_w="474" data-ori_h="382" style="width:237px;height:191px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/3qIWxrt.png" alt="[圖]" /></div>
三個按鈕以上會變成上下排列
style: 設定 .cancel 的按鈕會被放在最下面
<span style="color:#00F000">加上文字輸入框</span>
例如要做登入功能,讓使用者輸入帳號與密碼
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">alert</span> <span class="p">=</span> <span class="bp">UIAlertController</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"登入"</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="s">"請輸入帳號密碼"</span><span class="p">,</span> <span class="n">preferredStyle</span><span class="p">:</span> <span class="p">.</span><span class="n">alert</span><span class="p">)</span>
<span class="c1">// 加上文字輸入框</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addTextField</span> <span class="p">{</span> <span class="n">textField</span> <span class="k">in</span>
<span class="n">textField</span><span class="p">.</span><span class="n">placeholder</span> <span class="p">=</span> <span class="s">"帳號"</span>
<span class="p">}</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addTextField</span> <span class="p">{</span> <span class="n">textField</span> <span class="k">in</span>
<span class="n">textField</span><span class="p">.</span><span class="n">placeholder</span> <span class="p">=</span> <span class="s">"密碼"</span>
<span class="n">textField</span><span class="p">.</span><span class="n">isSecureTextEntry</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="c1">// 加上按鈕</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"取消"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="n">cancel</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">))</span>
<span class="n">alert</span><span class="p">.</span><span class="n">addAction</span><span class="p">(</span><span class="bp">UIAlertAction</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="s">"確定"</span><span class="p">,</span> <span class="n">style</span><span class="p">:</span> <span class="p">.</span><span class="k">default</span><span class="p">,</span> <span class="n">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">action</span> <span class="k">in</span>
<span class="c1">//點了確定後要做的事</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">usernameTextField</span> <span class="p">=</span> <span class="n">alert</span><span class="p">.</span><span class="n">textFields</span><span class="p">?[</span><span class="mi">0</span><span class="p">],</span>
<span class="kd">let</span> <span class="nv">passwordTextField</span> <span class="p">=</span> <span class="n">alert</span><span class="p">.</span><span class="n">textFields</span><span class="p">?[</span><span class="mi">1</span><span class="p">]</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"輸入的帳號為: </span><span class="si">\(</span><span class="n">usernameTextField</span><span class="p">.</span><span class="n">text</span><span class="p">!</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"輸入的密碼為: </span><span class="si">\(</span><span class="n">passwordTextField</span><span class="p">.</span><span class="n">text</span><span class="p">!</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}))</span>
<span class="kc">self</span><span class="p">.</span><span class="n">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="n">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
</div>
執行結果
<div class="img" data-ori_w="462" data-ori_h="328" style="width:231px;height:164px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/GothaQu.png" alt="[圖]" /></div>
參考
Swift 起步走 <a href="https://itisjoe.gitbooks.io/swiftgo/content/uikit/uialertcontroller.html" target="_blank" rel="nofollow">提示框 UIAlertController</a>
AppCoda <a href="http://www.appcoda.com/uialertcontroller-swift-closures-enum/" target="_blank" rel="nofollow">Introduction to UIAlertController, Swift Closures and Enumeration</a>
StackOverflow <a href="http://stackoverflow.com/questions/24022479/how-would-i-create-a-uialertview-in-swift" target="_blank" rel="nofollow">How would I create a UIAlertView in Swift?</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-26 23:43:37</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-17 00:35:48</span></div></pre>
Knuckles
[Xcode][Swift3] 新增 .gitignore 略過不需加入 git 的檔案
http://disp.cc/b/11-9WnE
2017-03-22T14:07:28+08:00
2017-05-10T10:28:02+08:00
專案目錄會有一些系統檔案或是編譯時的暫存檔
不想把這些檔案加到 Git 記錄,
也不想每次 Commit 時都看到這些檔案
新增一個 .gitignore 檔
在 Xcode 雙擊專案後選「New File...」
選最下方的「Empty」後點「Next」
檔名輸入「.gitignore」後
Where 要選儲存專案的資料夾,點「Create」
出現警告訊息,開頭為"."的檔案是給系統做隱藏檔用的
點「Use "."」確定要用這個檔名
在 Finder 按 command+shift+. 顯示隱藏檔
確認一下 .gitignore 跟 .git 資料夾在同個目錄
輸入 .gitignore 內容
.gitignore 的內容可以使用 https://www.gitignore.io/ 來產生
例如輸入「osx xcode swift」後,點「Create」
產生的內容為 https://www.gitignore.io/api/osx,xcode,swift
# Created by https://www.gitignore.io/api/os ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 新增 .gitignore 略過不需加入 git 的檔案<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-22 Wed. 14:07:23</div><hr color="#008080" />───────────────────
專案目錄會有一些系統檔案或是編譯時的暫存檔
不想把這些檔案加到 Git 記錄,
也不想每次 Commit 時都看到這些檔案
<div class="img" data-ori_w="326" data-ori_h="366" style="width:163px;height:183px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/CZt2ojI.png" alt="[圖]" /></div>
<span style="color:#00F000">新增一個 .gitignore 檔</span>
在 Xcode 雙擊專案後選「New File...」
<div class="img" data-ori_w="530" data-ori_h="263" style="width:265px;height:132px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/4tGH0OJ.png" alt="[圖]" /></div>
選最下方的「Empty」後點「Next」
<div class="img" data-ori_w="1126" data-ori_h="795" style="width:563px;height:398px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7Ov0t6U.png" alt="[圖]" /></div>
檔名輸入「.gitignore」後
Where 要選儲存專案的資料夾,點「Create」
<div class="img" data-ori_w="650" data-ori_h="496" style="width:325px;height:248px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/U0fqzoX.png" alt="[圖]" /></div>
出現警告訊息,開頭為"."的檔案是給系統做隱藏檔用的
點「Use "."」確定要用這個檔名
<div class="img" data-ori_w="628" data-ori_h="186" style="width:314px;height:93px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hMLvs82.png" alt="[圖]" /></div>
在 Finder 按 command+shift+. 顯示隱藏檔
確認一下 .gitignore 跟 .git 資料夾在同個目錄
<div class="img" data-ori_w="655" data-ori_h="174" style="width:328px;height:87px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/NE3cz3T.png" alt="[圖]" /></div>
<span style="color:#00F000">輸入 .gitignore 內容</span>
.gitignore 的內容可以使用 <a href="https://www.gitignore.io/" target="_blank" rel="nofollow">https://www.gitignore.io/</a> 來產生
<div class="img" data-ori_w="1093" data-ori_h="322" style="width:547px;height:161px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kvla9I2.png" alt="[圖]" /></div>
例如輸入「osx xcode swift」後,點「Create」
<div class="img" data-ori_w="1085" data-ori_h="307" style="width:543px;height:154px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/r4WsFXy.png" alt="[圖]" /></div>
產生的內容為 <a href="https://www.gitignore.io/api/osx%2Cxcode%2Cswift" target="_blank" rel="nofollow">https://www.gitignore.io/api/osx,xcode,swift</a>
<div class="highlight"><span></span><span class="c1"># Created by <a href="https://www.gitignore.io/api/osx,xcode,swift" target="_blank" rel="nofollow">https://www.gitignore.io/api/osx,xcode,swift</a></span>
<span class="c1">### OSX ###</span>
*.DS_Store
.AppleDouble
.LSOverride
<span class="c1"># Icon must end with two \r</span>
Icon
<span class="c1"># Thumbnails</span>
._*
<span class="c1"># Files that might appear in the root of a volume</span>
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
<span class="c1"># Directories potentially created on remote AFP share</span>
.AppleDB
.AppleDesktop
Network<span class="w"> </span>Trash<span class="w"> </span>Folder
Temporary<span class="w"> </span>Items
.apdisk
<span class="c1">### Swift ###</span>
<span class="c1"># Xcode</span>
<span class="c1">#</span>
<span class="c1"># gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore</span>
<span class="c1">## Build generated</span>
build/
DerivedData/
<span class="c1">## Various settings</span>
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
<span class="c1">## Other</span>
*.moved-aside
*.xccheckout
*.xcscmblueprint
<span class="c1">## Obj-C/Swift specific</span>
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
<span class="c1">## Playgrounds</span>
timeline.xctimeline
playground.xcworkspace
<span class="c1"># Swift Package Manager</span>
<span class="c1">#</span>
<span class="c1"># Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.</span>
<span class="c1"># Packages/</span>
<span class="c1"># Package.pins</span>
.build/
<span class="c1"># CocoaPods</span>
<span class="c1">#</span>
<span class="c1"># We recommend against adding the Pods directory to your .gitignore. However</span>
<span class="c1"># you should judge for yourself, the pros and cons are mentioned at:</span>
<span class="c1"># <a href="https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control" target="_blank" rel="nofollow">https://guides.cocoapods.org/using/u...-directory-into-source-control</a></span>
<span class="c1">#</span>
<span class="c1"># Pods/</span>
<span class="c1"># Carthage</span>
<span class="c1">#</span>
<span class="c1"># Add this line if you want to avoid checking in source code from Carthage dependencies.</span>
<span class="c1"># Carthage/Checkouts</span>
Carthage/Build
<span class="c1"># fastlane</span>
<span class="c1">#</span>
<span class="c1"># It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the</span>
<span class="c1"># screenshots whenever they are needed.</span>
<span class="c1"># For more information about the recommended setup visit:</span>
<span class="c1"># <a href="https://docs.fastlane.tools/best-practices/source-control/#source-control" target="_blank" rel="nofollow">https://docs.fastlane.tools/best-practices/source-control/#source-control</a></span>
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
<span class="c1">### Xcode ###</span>
build
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
<span class="c1"># End of <a href="https://www.gitignore.io/api/osx,xcode,swift" target="_blank" rel="nofollow">https://www.gitignore.io/api/osx,xcode,swift</a></span>
</div>
執行 Commit 將 .gitignore 檔加進記錄
<div class="img" data-ori_w="1237" data-ori_h="610" style="width:619px;height:305px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nxkRiyp.png" alt="[圖]" /></div>
此時那些要略過的檔就不會再出現了
<span style="color:#00F000">自訂一個不要被加入 .git 的 .plist 檔</span>
例如要使用一些網站的 API 時
必需將自己申請到的 API Keys 隱藏起來才行
點專案裡面的資料夾,
新增一個 Property List 檔,名稱輸入「keys」
<div class="img" data-ori_w="1137" data-ori_h="802" style="width:569px;height:401px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/GSKrike.png" alt="[圖]" /></div>
注意檔案不能存在專案根目錄,要存在放程式的目錄裡
在 .gitignore 裡加入 keys.plist
<div class="img" data-ori_w="1082" data-ori_h="237" style="width:541px;height:119px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/91lzGqq.png" alt="[圖]" /></div>
在 keys.plist 檔裡輸入 API Keys
<div class="img" data-ori_w="1055" data-ori_h="272" style="width:528px;height:136px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/04TPx5G.png" alt="[圖]" /></div>
要讀取 API Keys 時,使用
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">keys</span><span class="p">:</span> <span class="bp">NSDictionary</span><span class="p">?</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">path</span> <span class="p">=</span> <span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span> <span class="s">"keys"</span><span class="p">,</span> <span class="n">ofType</span><span class="p">:</span> <span class="s">"plist"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">keys</span> <span class="p">=</span> <span class="bp">NSDictionary</span><span class="p">(</span><span class="n">contentsOfFile</span><span class="p">:</span> <span class="n">path</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nv">myApiKey</span> <span class="p">=</span> <span class="n">keys</span><span class="p">?[</span><span class="s">"myApiKey"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span> <span class="p">??</span> <span class="s">"{myApiKey}"</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"myApiKey: </span><span class="si">\(</span><span class="n">myApiKey</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
</div>
可以在 {myApiKey} 填入預設值,
當 keys.plist 不存在的時候就會使用預設值
之後 Commit 時,就不會把 keys.plist 加進去了
參考
StackOverflow <a href="http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects/33688681" target="_blank" rel="nofollow">Git ignore file for Xcode projects</a>
StackOverflow <a href="http://stackoverflow.com/questions/30803244/how-to-hide-api-keys-in-github-for-ios-swift-projects" target="_blank" rel="nofollow">How to hide API keys in GitHub for iOS (SWIFT) projects?</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-22 14:07:23</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-10 10:28:04</span></div></pre>
Knuckles
[Xcode][Swift3] 將專案上傳至 GitHub 與其他人共同開發
http://disp.cc/b/11-9Wj3
2017-03-21T20:58:14+08:00
2023-11-07T16:29:14+08:00
延續上一篇 [Xcode][Swift3] 使用 Git 版本控制系統 - KnucklesNote板 - Disp BBS
將專案使用 Git 做版本控制了
但這樣也只能自己用而已,要讓多人共同開發的話要找個網站上傳
這篇要記錄如何將專案上傳至 GitHub 網站
GitHub 是現在最多人使用的程式碼代管網站
使用免費方案的話程式碼必需公開分享,無限制上傳數量
若付費的話可以只限團隊成員存取
在 GitHub 新增 Repository
先在 GitHub 註冊一個帳號
帳號使用的方案選免費的就好
免費方案的限制就是上傳的程式碼都要設為公開
* 2020年後免費方案也可以設為私人了
點「New Repository」新增一個程式碼倉庫
倉庫名稱輸入「GitExample」
不要勾選建立 README,也不要加上 .gitignore 與 license
點「Create repository」
複製 https 網址
https://github.com/KnucklesHuang/GitExample.git
在 Xcode 新增一個 Remote
回到 Xcode,上傳前要先建 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 將專案上傳至 GitHub 與其他人共同開發<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-21 Tue. 20:58:10</div><hr color="#008080" />───────────────────
延續上一篇 <a href="https://disp.cc/b/KnucklesNote/9W7U" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Git 版本控制系統 - KnucklesNote板 - Disp BBS</a>
將專案使用 Git 做版本控制了
但這樣也只能自己用而已,要讓多人共同開發的話要找個網站上傳
這篇要記錄如何將專案上傳至 <a href="https://github.com" target="_blank" rel="nofollow">GitHub</a> 網站
GitHub 是現在最多人使用的程式碼代管網站
使用免費方案的話程式碼必需公開分享,無限制上傳數量
若付費的話可以只限團隊成員存取
<span style="color:#00F000">在 GitHub 新增 Repository</span>
先在 <a href="http://github.com" target="_blank" rel="nofollow">GitHub</a> 註冊一個帳號
<div class="img" data-ori_w="607" data-ori_h="271" style="width:607px;height:271px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/w1ulem9.png" alt="[圖]" /></div>
帳號使用的方案選免費的就好
<div class="img" data-ori_w="618" data-ori_h="604" style="width:618px;height:604px"><img style="max-width:100%;" src="https://i4.disp.cc/imgur/fqYeP14.png" alt="[圖]" /></div>
<s>免費方案的限制就是上傳的程式碼都要設為公開</s>
* 2020年後免費方案也可以設為私人了
點「New Repository」新增一個程式碼倉庫
<div class="img" data-ori_w="648" data-ori_h="98" style="width:324px;height:49px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/uK1oWzX.png" alt="[圖]" /></div>
倉庫名稱輸入「GitExample」
不要勾選建立 README,也不要加上 .gitignore 與 license
點「Create repository」
<div class="img" data-ori_w="1159" data-ori_h="949" style="width:580px;height:475px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/MgKWnE7.png" alt="[圖]" /></div>
複製 https 網址
<a href="https://github.com/KnucklesHuang/GitExample.git" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/GitExample.git</a>
<div class="img" data-ori_w="934" data-ori_h="160" style="width:467px;height:80px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/NZXBJEn.png" alt="[圖]" /></div>
<span style="color:#00F000">在 Xcode 新增一個 Remote</span>
回到 Xcode,上傳前要先建立一個 Remote
點「Source Control」/「GitExample - master」/「Configure GitExample」
<div class="img" data-ori_w="766" data-ori_h="270" style="width:383px;height:135px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oADVjPL.png" alt="[圖]" /></div>
選擇「Remotes」頁後,點左下角的「✚」,選「Add Remote...」
<div class="img" data-ori_w="1068" data-ori_h="720" style="width:534px;height:360px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/WKe7nhQ.png" alt="[圖]" /></div>
Remore Name 輸入「GitHub-GitExample」
Address 貼上剛剛複製的 https 網址,點「Add Remore」
<div class="img" data-ori_w="731" data-ori_h="186" style="width:366px;height:93px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/FsaXtGi.png" alt="[圖]" /></div>
建立好一個 Remote 了,點「Done」
<div class="img" data-ori_w="1066" data-ori_h="720" style="width:533px;height:360px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qSQ1LAF.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Push 上傳分支 master</span>
建立好 Remote,就可以使用 Push 上傳了
點「Source Control」/「Push...」
<div class="img" data-ori_w="458" data-ori_h="338" style="width:229px;height:169px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/aR9XasU.png" alt="[圖]" /></div>
選擇剛剛建立的 Remote/master,點「Push」
<div class="img" data-ori_w="684" data-ori_h="164" style="width:342px;height:82px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/FR1883f.png" alt="[圖]" /></div>
將目前使用的分支 master 上傳
輸入登入 GitHub 的帳號密碼,按「OK」
<div class="img" data-ori_w="809" data-ori_h="345" style="width:405px;height:173px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qnnMHPS.png" alt="[圖]" /></div>
帳號密碼只要輸入一次,之後 Xcode 會記得
可以在「Xcode」/「Preferences...」/「Accounts」裡修改或刪除
上傳成功後,回到 GitHub 重整一下看看
<a href="https://github.com/KnucklesHuang/GitExample" target="_blank" rel="nofollow">https://github.com/KnucklesHuang/GitExample</a>
<div class="img" data-ori_w="1603" data-ori_h="756" style="width:802px;height:378px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gAoaecj.png" alt="[圖]" /></div>
之後 Commit 時,可以在左下方勾選「Push to remote:」
<div class="img" data-ori_w="1241" data-ori_h="615" style="width:621px;height:308px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/RDgnBfN.png" alt="[圖]" /></div>
就可以在 Commit 時也上傳新的版本到 GitHub 上了
<span style="color:#00F000">使用 checkout 下載專案</span>
假設自己是另一個想一起開發 GitExample 的使用者
開啟 Xcode 時選擇「Check out an existing project」
<div class="img" data-ori_w="679" data-ori_h="635" style="width:340px;height:318px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/0zWhC3o.png" alt="[圖]" /></div>
或是點「Source Control」/「Check Out...」
<div class="img" data-ori_w="457" data-ori_h="216" style="width:229px;height:108px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/oUQ6yl1.png" alt="[圖]" /></div>
因為我們之前有新增 Remote 所以這邊會有 GitExample 可以選
<div class="img" data-ori_w="1138" data-ori_h="659" style="width:569px;height:330px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qipRYs1.png" alt="[圖]" /></div>
沒有新增 Remote 的話,先在 GitHub 網頁複製 https 網址
<div class="img" data-ori_w="654" data-ori_h="322" style="width:327px;height:161px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/o9Gsgvi.png" alt="[圖]" /></div>
貼在 repository location: 後點「Next」
<div class="img" data-ori_w="1128" data-ori_h="162" style="width:564px;height:81px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/MqweLa7.png" alt="[圖]" /></div>
輸入專案資料夾的名稱,這邊使用「GitExample2」以示區別
儲存的位置選擇個人資料夾裡的 Xcode 資料夾,點「Download」
<div class="img" data-ori_w="642" data-ori_h="250" style="width:321px;height:125px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/CnSlXvf.png" alt="[圖]" /></div>
會建立好一個存在 ~/Xcode/GitExample2/ 資料夾的 GitExample 專案
這樣就可以將 GitHub 上的程式下載至 Xcode 編輯了
不一定要是自己帳號下的程式,只要是公開的都可以下載來研究
來修改一下程式上傳新的版本看看
在 ViewController.swift 加上成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">testNewUserPush</span><span class="p">(){</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"This is from new user!!"</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行 Commit,左邊會看到有兩個打問號的檔案可以不用加
左下勾選「Push to remote: origin/master」,
點「Commit 1 File and Push」
<div class="img" data-ori_w="1241" data-ori_h="648" style="width:621px;height:324px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/bKGoU6a.png" alt="[圖]" /></div>
這樣就會上傳新的版本到 GitHub 上了
可以在 GitHub 網頁看看有沒有更新
回到原本存在 ~/Xcode/GitExample/ 的專案
這邊也在 ViewController.swift 新增一個成員函數看看
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">testOriginalUserPush</span><span class="p">(){</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"This is from original user!!"</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行 Commit and Push 看看,會執行 Commit 但 Push 會失敗
<div class="img" data-ori_w="794" data-ori_h="229" style="width:397px;height:115px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ZBPrTP1.png" alt="[圖]" /></div>
因為 GitHub 上的 repository 已經被另一個使用者更新了
必需先使用 Pull 下載新的版本
點選「Source Control」/「Pull...」
<div class="img" data-ori_w="458" data-ori_h="334" style="width:229px;height:167px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hP9Dldg.png" alt="[圖]" /></div>
選擇「GitHub-GitExample/master」,點「Pull」
<div class="img" data-ori_w="683" data-ori_h="160" style="width:342px;height:80px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/h3WsQT4.png" alt="[圖]" /></div>
發現有程式碼出現衝突,會標記紅色的「C」
需要解決衝突後才可以下載
<div class="img" data-ori_w="1558" data-ori_h="614" style="width:779px;height:307px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/y70XAlg.png" alt="[圖]" /></div>
點一下問號後,在下面的四個按鈕選一個處理方式:
先左再右、選左邊、選右邊、先右再左
點先左再右,表示兩個函數都要留下來,左邊的函數排前面,右邊的函數排後面
<div class="img" data-ori_w="1558" data-ori_h="615" style="width:779px;height:308px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/xqv6vgH.png" alt="[圖]" /></div>
發現這樣函數 testOriginalUserPush() 會少一個右大弧號,
可以手動補上去後再點「Pull」
<div class="img" data-ori_w="1559" data-ori_h="612" style="width:780px;height:306px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/k2TEKav.png" alt="[圖]" /></div>
這樣就將其他使用者上傳的版本更新進來了
接著再將我們的更新上傳上去
執行 Commit,左下角勾選「Push to remote: GitHub-GitExample/master」,
點「Commit 1 File and Push」
<div class="img" data-ori_w="1245" data-ori_h="616" style="width:623px;height:308px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/eGpPcTa.png" alt="[圖]" /></div>
成功的 Commit 並使用 Push 上傳了
想要新增 .gitignore 檔略過部份檔案的話,參考這篇
<a href="http://disp.cc/b/11-9WnE" target="_blank" rel="nofollow">[Xcode][Swift3] 新增 .gitignore 略過不需加入 git 的檔案 - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-21 20:58:10</span>
<span class="record">※ 編輯: Knuckles 時間: 2023-11-07 16:29:14 (台灣)</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 Git 版本控制系統
http://disp.cc/b/11-9W7U
2017-03-19T23:25:35+08:00
2017-05-23T00:17:25+08:00
Git 是現在最多人使用的分散式版本控制系統
對於多人合作或是一個人開發都有很大的幫助
值得花點時間研究一下
Xcode 已經有內建圖型化的 Git 版本控制功能
而且在開新專案的時候就已經自動建立好 Git 初始化了
很適合作為 Git 入門
只要在程式碼修改告一段落的時候,使用 Commit 提交記錄
之後隨時可以比對每次 Commit 之間程式碼的差異
當程式有問題時可以很容易的將部份程式碼回復到之前的狀態
還可以開 Branch 使程式碼的開發過程有不同的分支
隨時可以切換到另一個分支來開發
等某個分支開發完成時再合併到主要的分支上
使用 Git 開新專案
開一個測試用的專案來試試看 Git 的功能
新的專案選擇「Single View Application」
專案名稱輸入「GitExample」
存檔時預設就會勾選「Greate Git repository on My Mac」
建立好的專案資料夾中會有個隱藏的「.git」資料夾
這個叫做 Git repository
Git 的相關記錄都是放在這個資料夾裡面
因為是隱藏的,用 Finder 預設看不到
可以按 comma ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Git 版本控制系統<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-19 Sun. 23:25:31</div><hr color="#008080" />───────────────────
Git 是現在最多人使用的分散式版本控制系統
對於多人合作或是一個人開發都有很大的幫助
值得花點時間研究一下
Xcode 已經有內建圖型化的 Git 版本控制功能
而且在開新專案的時候就已經自動建立好 Git 初始化了
很適合作為 Git 入門
只要在程式碼修改告一段落的時候,使用 Commit 提交記錄
之後隨時可以比對每次 Commit 之間程式碼的差異
當程式有問題時可以很容易的將部份程式碼回復到之前的狀態
還可以開 Branch 使程式碼的開發過程有不同的分支
隨時可以切換到另一個分支來開發
等某個分支開發完成時再合併到主要的分支上
<span style="color:#00F000">使用 Git 開新專案</span>
開一個測試用的專案來試試看 Git 的功能
新的專案選擇「Single View Application」
專案名稱輸入「GitExample」
<div class="img" data-ori_w="720" data-ori_h="458" style="width:360px;height:229px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YYW2INz.png" alt="[圖]" /></div>
存檔時預設就會勾選「Greate Git repository on My Mac」
<div class="img" data-ori_w="1137" data-ori_h="712" style="width:569px;height:356px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/3KElVhe.png" alt="[圖]" /></div>
建立好的專案資料夾中會有個隱藏的「.git」資料夾
這個叫做 Git repository
Git 的相關記錄都是放在這個資料夾裡面
因為是隱藏的,用 Finder 預設看不到
可以按 command+shift+. 顯示隱藏檔
<div class="img" data-ori_w="982" data-ori_h="346" style="width:491px;height:173px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/5WziBx9.png" alt="[圖]" /></div>
專案建立好的時候,Xcode 就自動做好第一次 Commit 了
可以點上方功能表的「Source Control」/「History...」
<div class="img" data-ori_w="449" data-ori_h="535" style="width:225px;height:268px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/XYsgsAi.png" alt="[圖]" /></div>
查看 Commit 記錄,已經有 Commit 一次了
<div class="img" data-ori_w="1106" data-ori_h="710" style="width:553px;height:355px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/q6pbDOI.png" alt="[圖]" /></div>
<span style="color:#00F000">在已建立的專案加上 Git</span>
如果專案在建立的時候沒有選擇「Greate Git repository on My Mac」
只要點「Source Control」/「Create Working Copy...」
<div class="img" data-ori_w="457" data-ori_h="474" style="width:229px;height:237px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LbNlN1a.png" alt="[圖]" /></div>
選擇專案後按「Create」
<div class="img" data-ori_w="852" data-ori_h="563" style="width:426px;height:282px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/KvksLzf.png" alt="[圖]" /></div>
就會在專案目錄下加上 .git 資料夾
並且自動執行一次 Commit 了
<span style="color:#00F000">使用 Commit 提交記錄</span>
修改一下程式來自己 Commit 看看
修改 ViewController.swift
在類別裡加上一個成員函數 sayHello()
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">sayHello</span><span class="p">()</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">)</span>
<span class="p">}</span>
</div>
然後在成員函數 viewDidLoad() 裡執行
<div class="highlight"><span></span> <span class="n">sayHello</span><span class="p">()</span>
</div>
修改好程式後,過一會會在程式檔的右邊看到被標了一個「M」符號
<div class="img" data-ori_w="412" data-ori_h="335" style="width:206px;height:168px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/poxJihe.png" alt="[圖]" /></div>
代表這個檔案從上一次 Commit 後有修改過
現在來使用 Commit 提交新的記錄
點上方功能表的「Source Control」/「Commit...」
會出現 Commit 確認視窗
<div class="img" data-ori_w="1693" data-ori_h="856" style="width:847px;height:428px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/1jELV11.png" alt="[圖]" /></div>
左欄會列出有修改過的檔案,若有檔案不想加進記錄的可以取消勾選
點一下檔案後,右欄會列出程式碼有修改過的地方
左邊的程式碼是目前修改過的,右邊的程式碼是上一次 Commit 時的
中間列了 1 和 2 是代表第幾個修改過的地方
旁邊打勾代表要記錄這個修改,取消勾選代表這邊不要加進記錄
點擊數字右邊的下拉選單
<div class="img" data-ori_w="380" data-ori_h="126" style="width:190px;height:63px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/xNa4aQJ.png" alt="[圖]" /></div>
可以選擇「Don't Commit」這個地方不要加進記錄,
或是「Discard Change」捨棄這個變更,程式碼會被改回上次 Commit 的記錄
確認好要 Commit 的地方後,下方可以對這次的 Commit 加上說明
例如寫上「測試看看 Commit,加上了一個 sayHello() 的功能」
這樣之後看記錄就知道這次 Commit 做了什麼事情了
最後按「Commit 1 File」即可完成提交記錄
可以注意到檔案右邊的「M」符號消失了
再看一下「Source Control」/「History...」
<div class="img" data-ori_w="1111" data-ori_h="324" style="width:556px;height:162px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tcAoXhH.png" alt="[圖]" /></div>
可以看到多了一筆記錄了
<span style="color:#00F000">選原程式碼至上一次 Commit 記錄</span>
Commit 後就可以放心的對程式碼做任意的修改測試
可以在專案設定修改參數、在 storyboard 調整元件、或刪除檔案
只要點一下「Source Control」/「Discard All Changes...」
<div class="img" data-ori_w="457" data-ori_h="538" style="width:229px;height:269px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/DrpeBhp.png" alt="[圖]" /></div>
就可以將專案完全回復至上一次 Commit 的狀態了
如果只有要將單一檔案回復的話,
在檔案右邊出現「M」符號後,選取檔案
點選「Source Control」/「Discard Changes in Selected Files...」即可
<div class="img" data-ori_w="542" data-ori_h="542" style="width:271px;height:271px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kfsmWrW.png" alt="[圖]" /></div>
不過要注意 Discard Changes 的變更就無法再復原囉
點下去後 Xcode 會再次確認
<div class="img" data-ori_w="626" data-ori_h="176" style="width:313px;height:88px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ipmHKlg.png" alt="[圖]" /></div>
<span style="color:#00F000">還原部份程式碼至某次 Commit 記錄</span>
若是想要查看之前的 Commit 記錄,或是回復部份變更的話
可以點右上角的「version editor」
<div class="img" data-ori_w="1515" data-ori_h="837" style="width:758px;height:419px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/O1uRhIs.png" alt="[圖]" /></div>
右邊會顯示上一次 Commit 時記錄的程式碼
因為我們剛剛才 Commit 所以沒有任何變更
可以點右下角的時鐘,換成第一次 Commit 的版本
<div class="img" data-ori_w="1507" data-ori_h="834" style="width:754px;height:417px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7MsxOuO.png" alt="[圖]" /></div>
點中間的 1 或 2 的下拉選單,可以選「Discard Change」
將該處程式碼還原為第一次 Commit 的版本
Xcode 目前沒有提供直接回復至上上次之前 Commit 的功能
只能將變更的地方一個一個的點選還原
真的想完全回復至某次 Commit 的話,可以在終端機使用指令模式
$ cd ~/xcode/GitExample/
$ git reset --hard 7d7e500
強制將整個專案還原至 7d7e500 時的記錄
注意這樣 7d7e500 之後的 Commit 記錄也都會消失了
<span style="color:#00F000">在 Git 新增檔案</span>
再來試試看新增檔案
點 Command+n 新增一個 Cocoa Touch Class
Class: TestClass
Subclass of: NSObject
<div class="img" data-ori_w="738" data-ori_h="236" style="width:369px;height:118px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LcJ5rKx.png" alt="[圖]" /></div>
可以看到檔案右邊有個「A」的符號,代表這是新增的檔案
<div class="img" data-ori_w="412" data-ori_h="362" style="width:206px;height:181px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/l4etr6z.png" alt="[圖]" /></div>
執行 Commit
<div class="img" data-ori_w="1247" data-ori_h="672" style="width:624px;height:336px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/M3p1IiY.png" alt="[圖]" /></div>
可以看到有兩個檔案需要加進記錄
因為新增檔案後專案設定會改變,所以也要記錄專案的改變
點 TestClass.swift 右邊只有顯示目前的程式碼
因為是新增的檔案,之前的 Commit 記錄沒有這個程式碼
下方輸入 Commit 註解後,點「Commit 2 Files」
<span style="color:#00F000">新增分支 Branch</span>
當想要測試新功能,但又怕把本來的程式弄壞
希望隨時可以換回本來的程式
這時就可以使用分支的功能
點「Source Control」,可以看到目前使用的分支為「master」
選擇「New Branch...」新增分支
<div class="img" data-ori_w="762" data-ori_h="534" style="width:381px;height:267px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rFAmAKG.png" alt="[圖]" /></div>
輸入分支名稱為「TestBranch」,按「Create」
<div class="img" data-ori_w="618" data-ori_h="226" style="width:309px;height:113px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/jm3Nk4D.png" alt="[圖]" /></div>
建立好新的分支後會自動切換至新的分支
程式碼還沒 Commit 的修改也會移至新的分支
修改程式看看,在 ViewController.swift,加上成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">testBranch</span><span class="p">()</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"This is test Branch!!"</span><span class="p">)</span>
<span class="p">}</span>
</div>
接著切換回分支 master 看看
切換分支前要先 commit 才能切換
執行 commit 後,
點「Source Control」/「TestBranch」/「Switch to Branch...」
<div class="img" data-ori_w="848" data-ori_h="336" style="width:424px;height:168px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kTzQ5Ld.png" alt="[圖]" /></div>
選擇分支「master」後,點「Switch」
<div class="img" data-ori_w="859" data-ori_h="574" style="width:430px;height:287px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yhNchIH.png" alt="[圖]" /></div>
可以看到剛剛新增的成員函數 testBranch() 消失了
程式碼回到了分支 master 最後一次 commit 的記錄
此時可以在分支 master 同步做其他的修改
例如加上一個成員函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">testMaster</span><span class="p">()</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"This is master Branch!!"</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行 Commit
此時分支 master 與分支 testBranch 有了不同的修改記錄
可以使用 version editor 來比較各分支的差異
打開 version editor,右下角選擇分支 TestBranch 的最新記錄
<div class="img" data-ori_w="1571" data-ori_h="903" style="width:786px;height:452px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/sfczHdn.png" alt="[圖]" /></div>
可以看到兩邊各自有新增的成員函數
<span style="color:#00F000">使用 merge 合併兩個分支</span>
當分支 testBranch 的程式寫好沒什麼問題後,
想要將變更合併到分支 master
可以在分支 master 執行 Commit 後,
點「Source Control」/「master」/「Merge from Branch...」
<div class="img" data-ori_w="762" data-ori_h="334" style="width:381px;height:167px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/E2t8g0a.png" alt="[圖]" /></div>
選「TestBranch」後,點「Merge」
<div class="img" data-ori_w="854" data-ori_h="561" style="width:427px;height:281px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/3opgR7s.png" alt="[圖]" /></div>
此時檔案的變更可能會衝突,
要調整分支 TestBranch 要怎麼分併進來
<div class="img" data-ori_w="1482" data-ori_h="795" style="width:741px;height:398px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/GjCwwOP.png" alt="[圖]" /></div>
每項變更有四種選項:
先左再右、選左邊、選右邊、先右再左
依照需求調整要怎麼分併後,點「Merge」
即可將分支 TestBranch 的修改合併至分支 master 了
<span style="color:#00F000">從某次 Commit 記錄開新分支</span>
如果忽然想要回到以前某次 Commit 的狀態另外開發,
但又不想捨棄現在的開發記錄,隨時還想要再切換回來
這時可以從某次 Commit 再開一個分支出來
不過 Xcode 沒有提供這個功能,要在終端機使用指令模式才行
例如我想從第二次 Commit 加上 sayHello() 功能時開新分支,
新的分支叫做 testSayHello
先在 History 複製 Commit 編號 c4ea354
<div class="img" data-ori_w="1109" data-ori_h="713" style="width:555px;height:357px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/pdmba2U.png" alt="[圖]" /></div>
在終端機執行
$ cd ~/xcode/GitExample/
$ git checkout -b testSayHello c4ea354
回到 Xcode
可以看到專案的程式碼已回復到第二次 Commit 的狀態,
並且已切換到 testSayHello 分支
想要回到之前的開發進度時,再切回分支 master 即可
<span style="color:#00F000">出現 Missing Files warnings</span>
Xcode 的 bug,當刪除或更改檔案名稱前沒有先 Commit 的時候可能會發生
在檔案的右邊會顯示一個驚嘆號
參考 <a href="https://stackoverflow.com/questions/39711061/xcode-8-missing-files-warnings" target="_blank" rel="nofollow">StackOverflow</a>
使用終端機在專案目錄執行以下指令即可
$ git add .
想要將程式碼上傳至 GitHub 與其他人共用的話,請看下一篇
<a href="http://disp.cc/b/11-9Wj3" target="_blank" rel="nofollow">[Xcode][Swift3] 將專案上傳至 GitHub 與其他人共同開發 - KnucklesNote板 - Disp BBS</a>
想要加上 .gitignore 檔略過部份檔案的話,看這篇
<a href="http://disp.cc/b/11-9WnE" target="_blank" rel="nofollow">[Xcode][Swift3] 新增 .gitignore 略過不需加入 git 的檔案 - KnucklesNote板 - Disp BBS</a>
參考
CocoaChina <a href="http://www.cocoachina.com/ios/20140524/8536.html" target="_blank" rel="nofollow">在Xcode中使用Git进行源码版本控制</a>
RayWenderlich <a href="https://www.raywenderlich.com/51351/how-to-use-git-source-control-with-xcode-in-ios-7" target="_blank" rel="nofollow">How To Use Git Source Control with Xcode in iOS 7</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-19 23:25:31</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-23 00:17:36</span></div></pre>
Knuckles
[Xcode][Swift3] 調整 Navigation,TableView,WebView 顏色
http://disp.cc/b/11-9VUg
2017-03-17T18:33:33+08:00
2017-05-17T01:16:48+08:00
修改 Navigation Bar 的顏色
在 storyboard 點一下 Navigation Controller 裡的 Navigation Bar
在屬性檢視器,取消勾選「Translucent」不要加上透明度
Bar Tint 選深藍色(#000080) 就是整條的背景色
Title Color 選白色,代表顯示文字的顏色
Tint 選白色,代表圖示的顏色
只要調整 Naviation Bar 的屬性檢視器後,就會跳出一個警告訊息
「Frame for "Navigation Bar" will be different at run time.」
就算把設定改回來也不會消失,應該是個 bug
不過只要在 Navigation Controller 的屬性檢視器
取消勾選「Adjust Scroll View Insets」然後再勾回來
錯誤訊息就會消失了... 參考 StackOverflow
執行結果
修改狀態列的顏色
發現上方狀態列使用黑色會看不清楚
要改成白色的話,參考 StackOverflow
到專案設定的 Info 點一下任意一項後面的✚
...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 調整 Navigation Bar 顏色<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-17 Fri. 18:33:30</div><hr color="#008080" />───────────────────
<span style="color:#00F000">修改 Navigation Bar 的顏色</span>
在 storyboard 點一下 Navigation Controller 裡的 Navigation Bar
在屬性檢視器,取消勾選「Translucent」不要加上透明度
Bar Tint 選深藍色(#000080) 就是整條的背景色
Title Color 選白色,代表顯示文字的顏色
Tint 選白色,代表圖示的顏色
<div class="img" data-ori_w="1242" data-ori_h="791" style="width:621px;height:396px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/zAxPu7T.png" alt="[圖]" /></div>
只要調整 Naviation Bar 的屬性檢視器後,就會跳出一個警告訊息
「Frame for "Navigation Bar" will be different at run time.」
就算把設定改回來也不會消失,應該是個 bug
不過只要在 Navigation Controller 的屬性檢視器
取消勾選「Adjust Scroll View Insets」然後再勾回來
錯誤訊息就會消失了... 參考 <a href="http://stackoverflow.com/questions/21382158/copied-a-view-to-a-project-with-navigation-controller-frame-for-image-view-w/39014669#39014669" target="_blank" rel="nofollow">StackOverflow</a>
<div class="img" data-ori_w="1238" data-ori_h="776" style="width:619px;height:388px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6bCTWZ9.png" alt="[圖]" /></div>
執行結果
<div class="img" data-ori_w="600" data-ori_h="103" style="width:300px;height:52px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/roOA8uI.png" alt="[圖]" /></div>
<span style="color:#00F000">修改狀態列的顏色</span>
發現上方狀態列使用黑色會看不清楚
要改成白色的話,參考 <a href="http://stackoverflow.com/questions/17678881/how-to-change-status-bar-text-color-in-ios-7" target="_blank" rel="nofollow">StackOverflow</a>
到專案設定的 Info 點一下任意一項後面的✚
<div class="img" data-ori_w="1332" data-ori_h="358" style="width:666px;height:179px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/uC9nz6p.png" alt="[圖]" /></div>
輸入「View controller-based status bar appearance」後按 Tab 選「NO」
<div class="img" data-ori_w="937" data-ori_h="230" style="width:469px;height:115px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yt7Mynr.png" alt="[圖]" /></div>
在專案設定的 General 將 Status Bar Style 改為「Light」
<div class="img" data-ori_w="1164" data-ori_h="683" style="width:582px;height:342px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/rfBQ3H1.png" alt="[圖]" /></div>
執行結果
<div class="img" data-ori_w="599" data-ori_h="102" style="width:300px;height:51px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/IBusCHa.png" alt="[圖]" /></div>
<span style="color:#00F000">修改 TableView 的顏色</span>
Table View 的屬性檢視器
將 Separator 的顏色改為 Dark Gray Color
View 的 Background 改為 Black Color
Table View Cell 的屬性檢視器
Selection 可以選擇選取的顏色,但只有 Blue, Gray, Default 可以選
View 的 Background 改為 Black Color
某些iOS版本在 storyboard 設定 Table View Cell 的背景色會失效
所以最好在 tableView(_:cellForRowAt:) 裡再加上
<div class="highlight"><span></span> <span class="n">cell</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">black</span>
</div>
要自訂選取的顏色的話,要修改程式
在 tableView(_:cellForRowAt:) 加上
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">cellBackgroundView</span> <span class="p">=</span> <span class="bp">UIView</span><span class="p">()</span>
<span class="n">cellBackgroundView</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="p">=</span> <span class="bp">UIColor</span><span class="p">.</span><span class="n">dark</span><span class="p">.</span><span class="n">Gray</span>
<span class="n">cell</span><span class="p">.</span><span class="n">selectedBackgroundView</span> <span class="p">=</span> <span class="n">cellBackgroundView</span>
</div>
執行結果
<div class="img" data-ori_w="523" data-ori_h="662" style="width:262px;height:331px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LEDODbH.png" alt="[圖]" /></div>
<span style="color:#00F000">修改 WebView 顏色</span>
要將 WebView 背景改為黑色,避免網頁載入前顯示白色
在 WebView 的屬性檢視器將 Background 設為 Black Color
並且取消勾選「Opaque」
<div class="img" data-ori_w="1486" data-ori_h="762" style="width:743px;height:381px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/DTfG4g6.png" alt="[圖]" /></div>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-17 18:33:30</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-05-17 01:16:53</span></div></pre>
Knuckles
[Xcode][Swift3] 新手教學 使用 Swift3 製作熱門文章瀏覽器 App
http://disp.cc/b/11-9VPx
2017-03-16T22:47:22+08:00
2017-03-22T23:04:24+08:00
製作一個熱門文章瀏覽器的 App
執行後會從網路下載並顯示 Disp BBS 最新的熱門文章列表
點擊想看的文章後,使用內嵌的瀏覽器在 App 中閱讀
點左上方的「回列表」可回到熱門文章列表繼續閱讀其他文章
只要修改一下就可以將自己的網站內容做成 App 讓人瀏覽了
此教學適合有程式基礎,剛開始學 Swift 語言的人
關於 Swift 語言的用法第一次用到時會稍微解釋一下
先照著做大概了解一下,之後再慢慢研究就好
此教學分為六篇文章
1. 使用 Swift3 開新專案
安裝 Xcode 8.2.1 使用 Swift 建立一個空白頁專案
2. 使用 Table View 產生列表頁
使用 Table View 產生列表頁,自訂列表的樣式
設定 Constraints 讓列表內容會隨螢幕大小調整
3. 安裝套件管理工具 CocoaPods
使用套件管理工具 CocoaPods 來管理下載的第三方類別庫
4. 使用 Alamofire 存取網站資料
使用 Alamofire 來下載熱門文章資料並顯示在 Table View
可以用下拉或點擊按鈕來更新列表內容
5. 點擊列表開啟並傳送資料至 ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 新手教學 使用 Swift3 製作熱門文章瀏覽器 App<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-16 Thu. 22:47:19</div><hr color="#008080" />───────────────────
製作一個熱門文章瀏覽器的 App
<div class="img" data-ori_w="1172" data-ori_h="652" style="width:586px;height:326px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/P0rXUzh.png" alt="[圖]" /></div>
執行後會從網路下載並顯示 Disp BBS 最新的熱門文章列表
點擊想看的文章後,使用內嵌的瀏覽器在 App 中閱讀
點左上方的「回列表」可回到熱門文章列表繼續閱讀其他文章
只要修改一下就可以將自己的網站內容做成 App 讓人瀏覽了
此教學適合有程式基礎,剛開始學 Swift 語言的人
關於 Swift 語言的用法第一次用到時會稍微解釋一下
先照著做大概了解一下,之後再慢慢研究就好
此教學分為六篇文章
1. <a href="http://disp.cc/b/11-9Ufe" target="_blank" rel="nofollow">使用 Swift3 開新專案</a>
<div class="img" data-ori_w="670" data-ori_h="542" style="width:335px;height:271px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yzw0Cg2.png" alt="[圖]" /></div>
安裝 Xcode 8.2.1 使用 Swift 建立一個空白頁專案
2. <a href="http://disp.cc/b/11-9UkW" target="_blank" rel="nofollow">使用 Table View 產生列表頁</a>
<div class="img" data-ori_w="524" data-ori_h="816" style="width:262px;height:408px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/KlUGiHs.png" alt="[圖]" /></div>
使用 Table View 產生列表頁,自訂列表的樣式
設定 Constraints 讓列表內容會隨螢幕大小調整
3. <a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">安裝套件管理工具 CocoaPods</a>
<div class="img" data-ori_w="882" data-ori_h="614" style="width:441px;height:307px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ecxndil.png" alt="[圖]" /></div>
使用套件管理工具 CocoaPods 來管理下載的第三方類別庫
4. <a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">使用 Alamofire 存取網站資料</a>
<div class="img" data-ori_w="526" data-ori_h="929" style="width:263px;height:465px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YUEVIK1.png" alt="[圖]" /></div>
使用 Alamofire 來下載熱門文章資料並顯示在 Table View
可以用下拉或點擊按鈕來更新列表內容
5. <a href="http://disp.cc/b/11-9VkP" target="_blank" rel="nofollow">點擊列表開啟並傳送資料至新的頁面</a>
<div class="img" data-ori_w="1641" data-ori_h="534" style="width:821px;height:267px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/WC8MJMS.png" alt="[圖]" /></div>
使用 Segue 讓列表點擊後可開啟新的頁面
並將選擇的文章資料帶至新的頁面
6. <a href="http://disp.cc/b/11-9VtJ" target="_blank" rel="nofollow">使用 WebView 顯示網頁內容</a>
<div class="img" data-ori_w="868" data-ori_h="636" style="width:434px;height:318px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tWEjUbN.png" alt="[圖]" /></div>
使用 Web View 將網頁內容顯示在內嵌的瀏覽器中
加上重新整理與回上頁的按鈕
需要參考已完成的專案的話 <a href="http://knuckles.disp.cc/app/swift/DispBBSv0.1.zip" target="_blank" rel="nofollow">點此可下載完整程式碼</a>
或是使用 <a href="https://github.com/KnucklesHuang/DispBBS-Swift/tree/HotTextBrowser" target="_blank" rel="nofollow">GitHub repository</a>
關於 Swift 的用法
可以參考 <a href="https://itisjoe.gitbooks.io/swiftgo/content/" target="_blank" rel="nofollow">Swift 起步走</a> (Swift 2.2)
還有 <a href="https://tommy60703.gitbooks.io/swift-language-traditional-chinese/" target="_blank" rel="nofollow">正體中文版蘋果 Swift 官方教學</a> (Swift 2.0)
但 Swift 2 和 3 的語法有些不相容,可再交互參考
<a href="https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html" target="_blank" rel="nofollow">Apple 官方教學</a> (Swift 3.1)
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-16 22:47:19</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-03-22 23:04:19</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 WebView 顯示網頁內容
http://disp.cc/b/11-9VtJ
2017-03-13T15:58:10+08:00
2017-03-19T11:43:30+08:00
延續上一篇 [Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面 - KnucklesNote板 - Disp BBS
開了一個空白的 View Controller
並傳送一個網址過來
使用 Web View 將網頁顯示出來
從右下的 Object library 拉一個 Web View 到 View Controller
先將 Web View 的大小調整為跟頁面一樣大,
再點右下的「Add New Constraints」按鈕,設定上下左右的間距為 0
然後點「Add 4 Constraints」
發現 Navigation Bar 的顏色變深了
因為當 Web View 加進一個有 Navigation Bar 的頁面時,
Web View 會自動在上方加上 Navigation Bar 的間距,
而 Navigation Bar 有設透明度所以顯示了 Web View 的背景色黑色
只要將 Web View 的背景色改為白色即可
使用 Assistant editor 自動加上 IBOutlet
要加上 IBOutlet,除了先手動加上程式碼
再到 story ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 WebView 顯示網頁內容<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-13 Mon. 15:58:07</div><hr color="#008080" />───────────────────
延續上一篇 <a href="http://disp.cc/b/11-9VkP" target="_blank" rel="nofollow">[Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面 - KnucklesNote板 - Disp BBS</a>
開了一個空白的 View Controller
並傳送一個網址過來
<span style="color:#00F000">使用 Web View 將網頁顯示出來</span>
從右下的 Object library 拉一個 Web View 到 View Controller
<div class="img" data-ori_w="868" data-ori_h="636" style="width:434px;height:318px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tWEjUbN.png" alt="[圖]" /></div>
先將 Web View 的大小調整為跟頁面一樣大,
再點右下的「Add New Constraints」按鈕,設定上下左右的間距為 0
然後點「Add 4 Constraints」
<div class="img" data-ori_w="850" data-ori_h="726" style="width:425px;height:363px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/UGMGkFR.png" alt="[圖]" /></div>
發現 Navigation Bar 的顏色變深了
因為當 Web View 加進一個有 Navigation Bar 的頁面時,
Web View 會自動在上方加上 Navigation Bar 的間距,
而 Navigation Bar 有設透明度所以顯示了 Web View 的背景色黑色
只要將 Web View 的背景色改為白色即可
<div class="img" data-ori_w="814" data-ori_h="438" style="width:407px;height:219px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/knyEAXE.png" alt="[圖]" /></div>
<span style="color:#00F000">使用 Assistant editor 自動加上 IBOutlet</span>
要加上 IBOutlet,除了先手動加上程式碼
再到 storyboard 的連結檢視器拉到對應的元件外
也可以使用 Assistant editor 快速產生
先點一下 Web View 然後點右上角的 Assistant editor 按鈕 (兩個圈圈那個)
在 storyboard 的右邊就會顯示元件對應的類別程式碼 TextViewController
若位置太小可以再按最右邊的 Utilities 按鈕先隱藏右邊的 Utilities 視窗
<div class="img" data-ori_w="1060" data-ori_h="394" style="width:530px;height:197px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/qBUVs6W.png" alt="[圖]" /></div>
按著 Ctrl 將 Web View 拉到放成員變數的地方
<div class="img" data-ori_w="1054" data-ori_h="565" style="width:527px;height:283px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gcnW88D.png" alt="[圖]" /></div>
輸入名稱後按 Connect
<div class="img" data-ori_w="1052" data-ori_h="488" style="width:526px;height:244px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LzllYZn.png" alt="[圖]" /></div>
自動產生程式碼並設定好與 storyboard 元件的連結了
<div class="img" data-ori_w="1059" data-ori_h="510" style="width:530px;height:255px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/dhALCiy.png" alt="[圖]" /></div>
要恢復本來的視窗,點一下 Standard editor 與 Utilities 即可
<div class="img" data-ori_w="857" data-ori_h="493" style="width:429px;height:247px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/RUhiALj.png" alt="[圖]" /></div>
編輯 TextViewController.swift
修改成員函數 viewDidLoad()
將之前加的 print(self.urlString) 改成
<div class="highlight"><span></span> <span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">urlString</span><span class="p">)</span>
<span class="kd">let</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">URLRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">url</span><span class="p">!)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">loadRequest</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
</div>
因為 url 取得的是一個可能為 nil 的 optional 變數
所以傳入 URLRequest() 前要在後面加個驚嘆號
代表確定這個變數不會是 nil 值
或者改用 if 取得一個不會是 nil 的變數也可以
<div class="highlight"><span></span> <span class="k">if</span> <span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="kc">self</span><span class="p">.</span><span class="n">urlString</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">request</span> <span class="p">=</span> <span class="n">URLRequest</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span>
<span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">loadRequest</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="p">}</span>
</div>
執行看看,點一下文章後,可以載入網頁內容了
<div class="img" data-ori_w="1114" data-ori_h="513" style="width:557px;height:257px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ON6NQm6.png" alt="[圖]" /></div>
<span style="color:#00F000">在載入網頁內容時顯示載入中圖示</span>
如果想要在 webView 存取網路時在左上角顯示載入中的圖示
<div class="img" data-ori_w="750" data-ori_h="46" style="width:375px;height:23px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ZOB5NVX.png" alt="[圖]" /></div>
要先加上 webView 的 delegate 函數
delegate 函數是一種將成員函數委任給另一個類別來寫的函數
例如 webView 載入檔案時會呼叫自己的 delegate 函數,
但函數內容是寫在別的類別 TextViewController 裡
修改 class TextViewController: UIViewController { 為
<div class="highlight"><span></span><span class="kd">class</span> <span class="nc">TextViewController</span><span class="p">:</span> <span class="bp">UIViewController</span><span class="p">,</span> <span class="bp">UIWebViewDelegate</span> <span class="p">{</span>
</div>
讓 TextViewController 繼承 UIWebViewDelegate
在成員函數 viewDidLoad() 中加上
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="kc">self</span>
</div>
讓 WebView 的成員變數 delegate 指向類別 TextViewController
加上兩個 webView 的 delegate 函數
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">webViewDidStartLoad</span><span class="p">(</span><span class="kc">_</span> <span class="n">webView</span><span class="p">:</span> <span class="bp">UIWebView</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">isNetworkActivityIndicatorVisible</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">webViewDidFinishLoad</span><span class="p">(</span><span class="kc">_</span> <span class="n">webView</span><span class="p">:</span> <span class="bp">UIWebView</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">isNetworkActivityIndicatorVisible</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>
</div>
當 webView 開始下載資料時,就會執行 webViewDidStartLoad()
下載完成時,就會執行 webViewFinishLoad()
在這兩個函數裡分別使用 UIApplication.shared.isNetworkActivityIndicatorVisible
來開啟及關閉載入中的圖示
<span style="color:#00F000">加上瀏覽功能的按鈕</span>
例如想要在右上角加上重新整理的按鈕
若 View Controller 裡沒有 Navigation Item 的話,要拉一個進來
<div class="img" data-ori_w="1338" data-ori_h="569" style="width:669px;height:285px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/vBPm8GV.png" alt="[圖]" /></div>
在 Navigation Item 的屬性檢視器,輸入 Title 為「閱讀文章」
<div class="img" data-ori_w="1334" data-ori_h="438" style="width:667px;height:219px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nuPZIwO.png" alt="[圖]" /></div>
拉一個 Bar Button Item 到 Navigation Item 的右邊
<div class="img" data-ori_w="947" data-ori_h="644" style="width:474px;height:322px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Lush69u.png" alt="[圖]" /></div>
點一下按鈕,在屬性檢視器將 System Item 改為 Refresh
<div class="img" data-ori_w="1337" data-ori_h="426" style="width:669px;height:213px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/kPUERTp.png" alt="[圖]" /></div>
打開 Assistant editor 按著 Ctrl 將按鈕拉到程式碼的類別裡
<div class="img" data-ori_w="1174" data-ori_h="794" style="width:587px;height:397px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Xla2QKY.png" alt="[圖]" /></div>
跳出的選單,Connection 改為 Action,名稱輸入 refresh,點 Connect
<div class="img" data-ori_w="561" data-ori_h="248" style="width:281px;height:124px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/G35C5tq.png" alt="[圖]" /></div>
自動產生了一個點擊按鈕後會執行的成員函數 refresh(_:)
<div class="img" data-ori_w="690" data-ori_h="88" style="width:345px;height:44px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nMSWW5q.png" alt="[圖]" /></div>
切換回 Standard editor,修改成員函數 refresh(_:) 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">refresh</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">reload</span><span class="p">()</span>
<span class="p">}</span>
</div>
執行看看,閱讀文章時點右上角的 refresh 按鈕重新整理網頁
一樣的方法再加一個回上頁的按鈕
拉一個 Bar Buttom Item 到 refresh 按鈕的左邊
在屬性檢視器輸入 Title 為「回上頁」
取消勾選「Enabled」,一開始先顯示為灰色的不可點擊狀態
<div class="img" data-ori_w="895" data-ori_h="658" style="width:448px;height:329px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Uk4CIj7.png" alt="[圖]" /></div>
開啟 Assistant editor
對回上頁按鈕按著 ctrl 拉一個 @IBOulet 到程式碼中成員變數的地方
名稱輸入 goBackBtn
<div class="highlight"><span></span> <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">goBackBtn</span><span class="p">:</span> <span class="bp">UIBarButtonItem</span><span class="p">!</span>
</div>
再對回上頁按鈕按著 ctrl 拉一個 @IBAction 到程式碼中
名稱輸入 goBack
修改產生的成員函數 goBack(_:) 為
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">goBack</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">goBack</span><span class="p">()</span>
<span class="p">}</span>
</div>
切換回 standard editor
修改 webView 的 delegate 函數 webViewDidFinishLoad(),加上
<div class="highlight"><span></span> <span class="k">if</span> <span class="kc">self</span><span class="p">.</span><span class="n">webView</span><span class="p">.</span><span class="n">canGoBack</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">goBackBtn</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">goBackBtn</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>
</div>
執行看看,進閱讀文章後,回上頁按鈕是灰色的不能點
在網頁中點擊超連結後,就會變成藍色的可以點
點了後會回到上一頁
參考
<a href="http://sourcefreeze.com/uiwebview-example-using-swift-in-ios/" target="_blank" rel="nofollow">http://sourcefreeze.com/uiwebview-example-using-swift-in-ios/</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-13 15:58:07</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-03-19 11:43:25</span></div></pre>
Knuckles
[Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面
http://disp.cc/b/11-9VkP
2017-03-11T21:31:58+08:00
2017-03-18T12:22:47+08:00
延續前一篇 [Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS
在 Table View 中載入熱門文章列表後,
接著要在點擊某一列後,開啟新的頁面,
並將文章網址傳至新的頁面
打開 storyboard
從右下的 Object library 拉一個 View Controller 進來
按著 Ctrl 將 HotTextCell 拉到新的 View Controller
放開後會跳出一個選單,選 Selection Segue 的 Show
兩個頁面間會出現一個箭頭,這個連接頁面的箭頭叫做 Segue (發音為 seg-way)
點一下後在屬性檢視器的 identifier 輸入「TextRead」,之後在程式會用到
執行看看,隨便點一篇文章就可以進到空白頁面,
按左上的回列表就可以回熱門文章頁
回到 storyboard
點一下 View Controller,在屬性檢視器輸入 Title:「閱讀文章」
Title 會顯示在頁面上方的 Navigation Bar
傳送資料至新的頁面
點 Command+n ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-11 Sat. 21:31:57</div><hr color="#008080" />───────────────────
延續前一篇 <a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
在 Table View 中載入熱門文章列表後,
接著要在點擊某一列後,開啟新的頁面,
並將文章網址傳至新的頁面
打開 storyboard
從右下的 Object library 拉一個 View Controller 進來
<div class="img" data-ori_w="1263" data-ori_h="761" style="width:632px;height:381px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yHsYTPK.png" alt="[圖]" /></div>
按著 Ctrl 將 HotTextCell 拉到新的 View Controller
放開後會跳出一個選單,選 Selection Segue 的 Show
<div class="img" data-ori_w="1220" data-ori_h="682" style="width:610px;height:341px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/bBrIKed.png" alt="[圖]" /></div>
兩個頁面間會出現一個箭頭,這個連接頁面的箭頭叫做 Segue (發音為 seg-way)
點一下後在屬性檢視器的 identifier 輸入「TextRead」,之後在程式會用到
<div class="img" data-ori_w="1641" data-ori_h="534" style="width:821px;height:267px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/WC8MJMS.png" alt="[圖]" /></div>
執行看看,隨便點一篇文章就可以進到空白頁面,
按左上的回列表就可以回熱門文章頁
<div class="img" data-ori_w="1161" data-ori_h="367" style="width:581px;height:184px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ODBit1y.png" alt="[圖]" /></div>
回到 storyboard
點一下 View Controller,在屬性檢視器輸入 Title:「閱讀文章」
Title 會顯示在頁面上方的 Navigation Bar
<div class="img" data-ori_w="1298" data-ori_h="351" style="width:649px;height:176px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/0in2GPQ.png" alt="[圖]" /></div>
<span style="color:#00F000">傳送資料至新的頁面</span>
點 Command+n 新增一個 Cocoa Touch Class 檔案
Class: TextViewController
Subclass of: UIViewController
<div class="img" data-ori_w="732" data-ori_h="238" style="width:366px;height:119px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Kavo9Rv.png" alt="[圖]" /></div>
設定 View Controller 的 Custom Class 為 TextViewController
<div class="img" data-ori_w="1246" data-ori_h="453" style="width:623px;height:227px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ewdaNmx.png" alt="[圖]" /></div>
編輯 TextViewController.swift
在 class TextViewController: UIViewController { 這行下面加上一個成員變數
<div class="highlight"><span></span> <span class="kd">var</span> <span class="nv">urlString</span><span class="p">:</span> <span class="nb">String</span><span class="p">!</span>
</div>
urlString 用來接收要顯示的網址
型態後面加驚嘆號代表這個變數一旦賦值後就不會再變為 nil
所以取值的時候可以不需要先用 if 判斷或是加上驚嘆號
再來要設定點擊列表某一列時,
將文章網址傳給 TextViewController 的成員變數 urlString
編輯 HotTextViewController.swift
最後面有一個註解掉的成員函數 prepare(for:sender:)
取消註解後修改為
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">prepare</span><span class="p">(</span><span class="k">for</span> <span class="n">segue</span><span class="p">:</span> <span class="bp">UIStoryboardSegue</span><span class="p">,</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">?)</span> <span class="p">{</span>
<span class="c1">// 1.</span>
<span class="k">if</span> <span class="n">segue</span><span class="p">.</span><span class="n">identifier</span> <span class="p">==</span> <span class="s">"TextRead"</span> <span class="p">{</span>
<span class="c1">// 2.</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">textViewController</span> <span class="p">=</span> <span class="n">segue</span><span class="p">.</span><span class="n">destination</span> <span class="k">as</span><span class="p">?</span> <span class="n">TextViewController</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">row</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">indexPathForSelectedRow</span><span class="p">?.</span><span class="n">row</span><span class="p">,</span>
<span class="kd">let</span> <span class="nv">hotText</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?[</span><span class="n">row</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="c1">// 3.</span>
<span class="n">textViewController</span><span class="p">.</span><span class="n">urlString</span> <span class="p">=</span> <span class="n">hotText</span><span class="p">[</span><span class="s">"url"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
prepare(for:sender:) 是在 storyboard 中,
使用 segue 設定點擊後連至其他頁面時會執行的函數
1. 可能會有不同的 segue,所以要先判斷是不是之前命名為 "TextRead" 的 segue
2. 先用 guard 確保三個常數有正確取到
textViewController 為 segue 連結到的目標 Controller
row 為目前點選的是 tableView 的第幾列
hotText 為第 row 列的資料
3. 取得 hotText["url"] 存至目標 Controller 的成員變數 urlString
修改 TextViewController.swift
確認一下是否有接收到列表傳來的網址
在成員函數 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="bp">print</span><span class="p">(</span><span class="kc">self</span><span class="p">.</span><span class="n">urlString</span><span class="p">)</span>
</div>
執行看看,點選某篇文章後,Console 視窗是否有顯示文章網址
<div class="img" data-ori_w="858" data-ori_h="206" style="width:429px;height:103px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/bWFd854.png" alt="[圖]" /></div>
要使用 WebView 將網頁使用內嵌的瀏覽器顯示出來,請看下一篇:
<a href="http://disp.cc/b/11-9VtJ" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 WebView 顯示網頁內容 - KnucklesNote板 - Disp BBS</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-11 21:31:57</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-03-18 12:22:43</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 Alamofire 存取網站資料
http://disp.cc/b/11-9UWG
2017-03-07T18:06:43+08:00
2017-03-29T22:15:31+08:00
延續上一篇 [Xcode][Swift3] 使用 Table View 產生列表頁 - KnucklesNote板 - Disp BBS
接下來要抓網路上的資料來顯示
要用 Swift 存取網路資料,有個好用的第三方類別庫 Alamofire
是與 Object-C 的 AFNetworking 同一個作者建立的
參考 GitHub: https://github.com/Alamofire/Alamofire
依照這篇 [Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS
使用 CocoaPods 安裝 Alamofire 後
為了要顯示網路上的圖檔,還要再安裝 AlamofireImage
參考 GitHub: https://github.com/Alamofire/AlamofireImage
修改專案資料夾下的 Podfile
在 pod 'Alamofire' 下再加上一行
pod 'AlamofireImage'
使用終端機,切換到專案資料夾執行 pod inst ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Alamofire 存取網站資料<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-07 Tue. 18:06:38</div><hr color="#008080" />───────────────────
延續上一篇 <a href="http://disp.cc/b/11-9UkW" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Table View 產生列表頁 - KnucklesNote板 - Disp BBS</a>
接下來要抓網路上的資料來顯示
要用 Swift 存取網路資料,有個好用的第三方類別庫 Alamofire
是與 Object-C 的 AFNetworking 同一個作者建立的
<div class="img" data-ori_w="850" data-ori_h="250" style="width:850px;height:250px"><img style="max-width:100%;" src="http://i.imgur.com/pgepnyS.png" alt="[圖]" /></div>
參考 GitHub: <a href="https://github.com/Alamofire/Alamofire" target="_blank" rel="nofollow">https://github.com/Alamofire/Alamofire</a>
依照這篇 <a href="http://disp.cc/b/11-9UJS" target="_blank" rel="nofollow">[Xcode][Swift3] 安裝套件管理工具 CocoaPods - KnucklesNote板 - Disp BBS</a>
使用 CocoaPods 安裝 Alamofire 後
為了要顯示網路上的圖檔,還要再安裝 AlamofireImage
參考 GitHub: <a href="https://github.com/Alamofire/AlamofireImage" target="_blank" rel="nofollow">https://github.com/Alamofire/AlamofireImage</a>
修改專案資料夾下的 Podfile
在 pod 'Alamofire' 下再加上一行
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'AlamofireImage'</span>
</div>
使用終端機,切換到專案資料夾執行 pod install
$ cd ~/Xcode/DispBBS
$ pod install
目前安裝的版本為
Alamofire 4.4.0
AlamofireImage 3.2.0
回到 Xcode
修改 HotTextViewController.swift
在 import UIKit 下加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">Alamofire</span>
<span class="kd">import</span> <span class="nc">AlamofireImage</span>
</div>
如果出現錯誤訊息「cannot load underlying module for 'Alamofire'
<div class="img" data-ori_w="932" data-ori_h="95" style="width:466px;height:48px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Bm0NJLV.png" alt="[圖]" /></div>
點一下「Product」/「Clean」就可以了
<div class="img" data-ori_w="379" data-ori_h="487" style="width:190px;height:244px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ryjginp.png" alt="[圖]" /></div>
<span style="color:#00F000">取消只能用加密連線的限制</span>
在 Xcode 7 之後的版本,預設網路存取要使用加密連線 (https://) 才行
否則會無法連線,並出現錯誤訊息「App transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.」
要取消限制,到專案設定的「info」,在「Custom iOS Target Properties」項目裡
點一下任一項目後面的✚
<div class="img" data-ori_w="1401" data-ori_h="640" style="width:701px;height:320px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/s0fui3r.png" alt="[圖]" /></div>
輸入「App Transport Security Settings」,按 Enter
<div class="img" data-ori_w="1073" data-ori_h="265" style="width:537px;height:133px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/X13XCxD.png" alt="[圖]" /></div>
點一下左邊的向右箭頭,變成下向箭頭後,點右邊的✚
<div class="img" data-ori_w="1083" data-ori_h="185" style="width:542px;height:93px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/331nV80.png" alt="[圖]" /></div>
選擇「Allow Arbitrary Loads」,按 Tab,選擇 Value 為「YES」
<div class="img" data-ori_w="1076" data-ori_h="270" style="width:538px;height:135px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/xDaa05N.png" alt="[圖]" /></div>
<span style="color:#00F000">熱門文章的 JSON 檔格式</span>
先看一下我們要抓的 JSON檔 <a href="http://disp.cc/api/hot_text.json" target="_blank" rel="nofollow">http://disp.cc/api/hot_text.json</a>
這是由 Server 端產生的 API,裡面的資料格式是由網頁後端程式(例如PHP)來產生的
格式為每個網站自訂的,所以要抓其他網站的資料時要再修改一下
可以用 Chrome 的網站管理工具將 JSON 檔用樹狀結構顯示出來看看
<div class="img" data-ori_w="1359" data-ori_h="1007" style="width:680px;height:504px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/fIXyP5i.png" alt="[圖]" /></div>
這個熱門文章列表的 JSON 格式是像這樣
<div class="highlight"><span></span><span class="p">{</span><span class="s2">"isSuccess"</span><span class="o">:</span><span class="mf">1</span><span class="p">,</span><span class="s2">"err"</span><span class="o">:</span><span class="mf">0</span><span class="p">,</span><span class="s2">"totalNum"</span><span class="o">:</span><span class="mf">28</span><span class="p">,</span><span class="s2">"list"</span><span class="o">:</span><span class="p">[</span><span class="nx">Row1</span><span class="p">,</span><span class="w"> </span><span class="nx">Row2</span><span class="p">,</span><span class="w"> </span><span class="p">...,</span><span class="w"> </span><span class="nx">RowN</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
</div>
最外層是一個JS物件{"key":value},
"isSuccess" 用來檢查資料有沒有產生成功,沒問題的話就是 1
"totalNum" 總共有幾篇熱門文章
"list" 為一個JS陣列[value],裡面存了N個JS物件,記錄了每篇熱門文章的資料
每個JS物件的格式是像這樣
<div class="highlight"><span></span><span class="w"> </span><span class="nx">Row1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="s2">"hot_num"</span><span class="o">:</span><span class="s2">"目前人氣值"</span><span class="p">,</span><span class="s2">"bi"</span><span class="o">:</span><span class="s2">"看板編號"</span><span class="p">,</span><span class="s2">"ti"</span><span class="o">:</span><span class="s2">"文章編號"</span><span class="p">,</span><span class="s2">"title"</span><span class="o">:</span><span class="s2">"文章標題"</span><span class="p">,</span><span class="s2">"board_name"</span><span class="o">:</span><span class="s2">"看板名稱"</span><span class="p">,</span><span class="s2">"ai"</span><span class="o">:</span><span class="s2">"作者編號"</span><span class="p">,</span><span class="s2">"author"</span><span class="o">:</span><span class="s2">"作者帳號"</span><span class="p">,</span><span class="s2">"desc"</span><span class="o">:</span><span class="s2">"文章摘要"</span><span class="p">,</span><span class="s2">"img_list"</span><span class="o">:</span><span class="p">[</span><span class="s2">"縮圖網址1"</span><span class="p">,</span><span class="s2">"縮圖網址2"</span><span class="p">,</span><span class="s2">"縮圖網址3"</span><span class="p">],</span><span class="w"> </span><span class="s2">"url"</span><span class="o">:</span><span class="s2">"文章網址"</span><span class="p">}</span>
</div>
在 Swift 裡,要將 JS物件 存成 Dictionary
將 JS陣件 存成 Array
<span style="color:#00F000">使用 Alamofire 讀取網站的 json 檔</span>
修改 HotTextViewController.swift
在 class HotTextViewController: UITableViewController { 這行下面加上
<div class="highlight"><span></span> <span class="c1">// 1. 新增成員變數</span>
<span class="kd">var</span> <span class="nv">hotTextArray</span><span class="p">:[</span><span class="nb">Any</span><span class="p">]?</span>
<span class="c1">// 2. 新增成員函數</span>
<span class="kd">func</span> <span class="nf">loadData</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">urlString</span> <span class="p">=</span> <span class="s">"https://disp.cc/api/hot_text.json"</span>
<span class="c1">// 3. 使用 Alamofire 存取網址</span>
<span class="n">Alamofire</span><span class="p">.</span><span class="n">request</span><span class="p">(</span><span class="n">urlString</span><span class="p">).</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">JSON</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="p">{</span>
<span class="c1">// 4.</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"JSON: </span><span class="si">\(</span><span class="n">JSON</span><span class="si">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
1. 新增一個成員變數 hotTextArray,型別為[Any],代表任意物件的陣列
型別後面加上問號代表是一個可以為 nil 值的 optional 變數
用來儲存列表的資料
2. 新增一個成員函數 loadData(),用來讀取網路上的 json 檔
使用 let urlString 代表宣告一個常數,urlString 的值之後不能再變動
3. 當 Alamofire.request() 執行完後,會使用非同步的方法執行 responseJSON()
responseJSON 傳入了一個 callback 匿名函數:{ 輸入值 in 函數內容 }
也就是 responseJSON({匿名函數}),
當參數只有傳入一個函數時可簡寫為:resposnseJSON {匿名函數}
4. 先將讀取到的 JSON 資料顯示在 Console 視窗,看看有沒有讀取成功
然後在成員函數 viewDidLoad() 裡加上
<div class="highlight"><span></span> <span class="n">loadData</span><span class="p">()</span>
</div>
viewDidLoad 是 view 載入後會執行的函數
在這邊呼叫 loadData() 載入資料
執行結果
<div class="img" data-ori_w="1218" data-ori_h="963" style="width:609px;height:482px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/vbcwGqz.png" alt="[圖]" /></div>
點程式碼左下角的按鈕可顯示下方的 Debug Area
點 Debug Area 右下角的按鈕可將左邊的 Variable View 隱藏
在 Console 視窗可看到用 print() 指令顯示的結果
可以確定網站的 json 檔有成功的讀取到了
修改成員函數 loadData() 將列表的資料存到成員變數 hotTextArray
<div class="highlight"><span></span> <span class="kd">func</span> <span class="nf">loadData</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">urlString</span> <span class="p">=</span> <span class="s">"https://disp.cc/api/hot_text.json"</span>
<span class="n">Alamofire</span><span class="p">.</span><span class="n">request</span><span class="p">(</span><span class="n">urlString</span><span class="p">).</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="c1">// 1.</span>
<span class="k">guard</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">errorMessage</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">?.</span><span class="n">localizedDescription</span>
<span class="bp">print</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">!)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">JSON</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">result</span><span class="p">.</span><span class="n">value</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"JSON formate error"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="c1">//print("JSON: \(JSON)")</span>
<span class="c1">// 2.</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">list</span> <span class="p">=</span> <span class="n">JSON</span><span class="p">[</span><span class="s">"list"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">Any</span><span class="p">]</span> <span class="p">{</span>
<span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span> <span class="p">=</span> <span class="n">list</span>
<span class="kc">self</span><span class="p">.</span><span class="n">tableView</span><span class="p">.</span><span class="n">reloadData</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
1. 使用 guard 檢查網路存取是否成功,若失敗的話使用 return 跳出
guard 條件 else { //條件失敗要做的事 }
等同於 if 條件 == nil { //條件失敗要做的事 }
第二個 guard 將 response.result.value 轉型為 [String: Any] 的 Dictionary,
轉型的 as? 後面加問號代表傳型失敗就直接傳回 nil 值
轉型後的結果存為常數 JSON
2. 讀取 JSON["list"],轉型為 [Any] 陣列,存為常數 list
成功的話將 list 存到成員變數 hotTextArray
有變更資料就要執行一次 reloadData(),更新 tableView 顯示的資料
修改成員函數 tableView(_:numberOfRowsInSection)
<div class="highlight"><span></span><span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="k">if</span> <span class="kd">let</span> <span class="nv">num</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?.</span><span class="bp">count</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">num</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="p">}</span>
</div>
依照陣列儲存的數目來顯示 tableView 的列數
因為 self.hotTextArray?.count 可能會取得 nil 值
所以不能直接 return self.hotTextArray?.count
修改成員函數 tableView(_:cellForRowAt)
<div class="highlight"><span></span><span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"HotTextCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">HotTextCell</span>
<span class="c1">// 1. 讀取第 indexPath.row 的值</span>
<span class="k">guard</span> <span class="kd">let</span> <span class="nv">hotText</span> <span class="p">=</span> <span class="kc">self</span><span class="p">.</span><span class="n">hotTextArray</span><span class="p">?[</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">Any</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span>
<span class="bp">print</span><span class="p">(</span><span class="s">"Get row </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s"> error"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
<span class="c1">// 2. 填入 Label 的值</span>
<span class="n">cell</span><span class="p">.</span><span class="n">titleLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="n">hotText</span><span class="p">[</span><span class="s">"title"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span>
<span class="n">cell</span><span class="p">.</span><span class="n">descLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="n">hotText</span><span class="p">[</span><span class="s">"desc"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="nb">String</span>
<span class="c1">// 3. 顯示縮圖</span>
<span class="kd">let</span> <span class="nv">img_list</span> <span class="p">=</span> <span class="n">hotText</span><span class="p">[</span><span class="s">"img_list"</span><span class="p">]</span> <span class="k">as</span><span class="p">?</span> <span class="p">[</span><span class="nb">String</span><span class="p">]</span>
<span class="kd">let</span> <span class="nv">placeholderImage</span> <span class="p">=</span> <span class="bp">UIImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="s">"displogo120"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">img_list</span><span class="p">?.</span><span class="bp">count</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nv">url</span> <span class="p">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="p">(</span><span class="n">img_list</span><span class="p">?[</span><span class="mi">0</span><span class="p">])</span><span class="o">!</span><span class="p">)</span><span class="o">!</span>
<span class="n">cell</span><span class="p">.</span><span class="n">thumbImageView</span><span class="p">?.</span><span class="n">af_setImage</span><span class="p">(</span><span class="n">withURL</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="n">placeholderImage</span><span class="p">:</span> <span class="n">placeholderImage</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">cell</span><span class="p">.</span><span class="n">thumbImageView</span><span class="p">?.</span><span class="n">image</span> <span class="p">=</span> <span class="n">placeholderImage</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
1. 讀取成員變數 hotTextArray 的第 indexPath.row 個值,
存成型態為 [String: Any] 的 Dictionary 變數 hotText
2. 讀取 hotText 中的值時,例如 hotText?["title"]
要加上 as? String 將型態由 Any 轉為 String
3. hotText 中的縮圖 img_list 是一個存有 0~3 個圖片網址的陣列
所以將型態由 Any 轉為 [String] 後存在常數 img_list
用 if 檢查若縮圖的數目不為 0,將第一個縮圖顯示出來
對 UIImageView 使用 AlamofireImage 提供的 af_setImage(withURL:placeholderImage:)
輸入圖片網址 url 與載入圖片前要先顯示的圖 placeholderImage
若縮圖數目為0,則使用之前存在專案裡的 displogo120 來顯示
執行結果
<div class="img" data-ori_w="526" data-ori_h="929" style="width:263px;height:465px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YUEVIK1.png" alt="[圖]" /></div>
<span style="color:#00F000">存取網路時顯示載入中的圖示</span>
要讓 Alamofire 在存取網路時自動在左上方狀態列顯示載入中圖示
<div class="img" data-ori_w="750" data-ori_h="46" style="width:375px;height:23px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ZOB5NVX.png" alt="[圖]" /></div>
要再另外安裝 AlamofireNetworkActivityIndicator
GitHub: <a href="https://github.com/Alamofire/AlamofireNetworkActivityIndicator" target="_blank" rel="nofollow">https://github.com/Alamofire/AlamofireNetworkActivityIndicator</a>
修改 Podfile 加上
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'AlamofireNetworkActivityIndicator'</span>
</div>
使用終端機在專案目錄執行
$ pod install
目前安裝的版本為 AlamofireNetworkActivityIndicator 2.1.0
修改 AppDelegate.swift
在 import UIKit 下一行加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">AlamofireNetworkActivityIndicator</span>
</div>
在第一個成員函數 application(_:didFinishLaunchingWithOption:)
的 return true 之前加上
<div class="highlight"><span></span> <span class="n">NetworkActivityIndicatorManager</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">isEnabled</span> <span class="p">=</span> <span class="kc">true</span>
<span class="n">NetworkActivityIndicatorManager</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">startDelay</span> <span class="p">=</span> <span class="mi">0</span>
</div>
這樣使用 Alamofire 存取網路時就會自動顯示載入中圖示了
startDelay 預設值是 1,代表網路開始存取後過一秒還在存取的話才顯示
若是想要網路開始存取時就馬上顯示的話可以改成 0
如果不是使用 Alamofire 時,也想顯示載入中圖示的話
可以使用內建的
<div class="highlight"><span></span> <span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">isNetworkActivityIndicatorVisible</span> <span class="p">=</span> <span class="kc">true</span>
</div>
要關閉時使用
<div class="highlight"><span></span> <span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">isNetworkActivityIndicatorVisible</span> <span class="p">=</span> <span class="kc">false</span>
</div>
<span style="color:#00F000">加入重整按鈕與下拉重整功能</span>
修改 HotTextViewController.swift
新增一個成員函數 refresh(_:)
<div class="highlight"><span></span> <span class="kr">@IBAction</span> <span class="kd">func</span> <span class="nf">refresh</span><span class="p">(</span><span class="kc">_</span> <span class="n">sender</span><span class="p">:</span> <span class="nb">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="n">loadData</span><span class="p">()</span>
<span class="p">}</span>
</div>
@IBAction 是用來連結按鈕點擊動作與成員函數用的
在 storyboard 拉一個 Bar Button Item 到 Navigation Bar 右邊
在屬性設定選擇 System Item: Refresh
<div class="img" data-ori_w="978" data-ori_h="771" style="width:489px;height:386px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/zFML6dQ.png" alt="[圖]" /></div>
在 Table View Controller 的連結設定
將 Received Actions 裡的 refresh: 右邊的圈圈拉到重整按鈕上
<div class="img" data-ori_w="1313" data-ori_h="670" style="width:657px;height:335px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/4ZoCedZ.png" alt="[圖]" /></div>
接著開啟 Table View 原本就內建的下拉重整功能 (pull-to-refresh)
在 Table View Controller 的屬性設定
將 Refreshing 設定為「Enabled」
下方 Title 設定置中,文字輸入「更新熱門文章」
<div class="img" data-ori_w="1309" data-ori_h="484" style="width:655px;height:242px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/RKQ5rUG.png" alt="[圖]" /></div>
修改 HotTextViewController.swift
在成員函數 viewDidLoad() 裡加上一行
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">?.</span><span class="n">addTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="k">#selector</span><span class="p">(</span><span class="n">refresh</span><span class="p">(</span><span class="kc">_</span><span class="p">:)),</span> <span class="k">for</span><span class="p">:</span> <span class="n">UIControlEvents</span><span class="p">.</span><span class="n">valueChanged</span><span class="p">)</span>
</div>
將 refreshControl 要執行的動作選擇我們剛剛加上的成員函數 refresh(_:)
然後修改載入資料的函數 loadData()
在 Alamofire.request(urlString).responseJSON { response in 的下一行加上
<div class="highlight"><span></span> <span class="kc">self</span><span class="p">.</span><span class="n">refreshControl</span><span class="p">?.</span><span class="n">endRefreshing</span><span class="p">()</span>
</div>
在下載完資料時使用 endRefreshing() 關閉載入中的圖示
執行看看
將列表往下拉時,就會出現「更新熱門文章」與載入中的圖示
放開後等載入完成就會關閉圖示
<div class="img" data-ori_w="524" data-ori_h="693" style="width:262px;height:347px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/hkLNkX7.png" alt="[圖]" /></div>
要在點擊列表之後,將取得的文章網址在另一個頁面顯示出來,請看下一篇:
<a href="http://disp.cc/b/11-9VkP" target="_blank" rel="nofollow">[Xcode][Swift3] 點擊列表開啟並傳送資料至新的頁面 - KnucklesNote板 - Disp BBS</a>
參考
AppCoda <a href="http://www.appcoda.com.tw/alamofire-beginner-guide/" target="_blank" rel="nofollow">Swift 網路程式設計指南:如何使用 Alamofire</a>
RayWenderlich <a href="https://www.raywenderlich.com/147086/alamofire-tutorial-getting-started-2" target="_blank" rel="nofollow">Alamofire Tutorial: Getting Started</a>
<a href="https://www.andrewcbancroft.com/2015/03/17/basics-of-pull-to-refresh-for-swift-developers/" target="_blank" rel="nofollow">Basics of Pull to Refresh for Swift Developers</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-07 18:06:38</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-03-29 22:14:59</span></div></pre>
Knuckles
[Xcode][Swift3] 安裝套件管理工具 CocoaPods
http://disp.cc/b/11-9UJS
2017-03-05T15:20:06+08:00
2018-08-21T00:30:56+08:00
要使用第三方類別庫的話,要安裝套件管理工具 CocoaPods
例如我們想要裝簡化網路存取功能的類別庫 Alamofire
在 Mac 安裝 CocoaPods
打開終端機,輸入以下指令
$ sudo gem install cocoapods
CocoaPods 是使用 Ruby 語言寫的,
gem 是安裝 Ruby 程式的指令,Mac 預設就有支援 Ruby
安裝好了之後,使用 cd 切換到專案的目錄
例如專案的資料是放在 帳號的家目錄/Xcode/DispBBS 的話,輸入
$ cd ~/Xcode/DispBBS
使用 CocoaPods 的指令 pod 在專案目錄建立一個 Podfile 檔
$ pod init
使用 vim 編輯 Podfile,加上想安裝的 Alamofire
$ vim Podfile
# Uncomment this line to define a global platform for your project
platform :ios, '9.0'
target 'DispBBS' do
# Co ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 安裝套件管理工具 CocoaPods<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-05 Sun. 15:19:55</div><hr color="#008080" />───────────────────
要使用第三方類別庫的話,要安裝套件管理工具 CocoaPods
<div class="img" data-ori_w="451" data-ori_h="112" style="width:451px;height:112px"><img style="max-width:100%;" src="http://i.imgur.com/WlGXnMY.png" alt="[圖]" /></div>
例如我們想要裝簡化網路存取功能的類別庫 Alamofire
<span style="color:#00F000">在 Mac 安裝 CocoaPods</span>
打開終端機,輸入以下指令
$ sudo gem install cocoapods
<div class="img" data-ori_w="882" data-ori_h="614" style="width:441px;height:307px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ecxndil.png" alt="[圖]" /></div>
CocoaPods 是使用 Ruby 語言寫的,
gem 是安裝 Ruby 程式的指令,Mac 預設就有支援 Ruby
安裝好了之後,使用 cd 切換到專案的目錄
例如專案的資料是放在 帳號的家目錄/Xcode/DispBBS 的話,輸入
$ cd ~/Xcode/DispBBS
使用 CocoaPods 的指令 pod 在專案目錄建立一個 Podfile 檔
$ pod init
使用 vim 編輯 Podfile,加上想安裝的 Alamofire
$ vim Podfile
<div class="highlight"><span></span><span class="c1"># Uncomment this line to define a global platform for your project</span>
<span class="w"> </span><span class="n">platform</span><span class="w"> </span><span class="ss">:ios</span><span class="p">,</span><span class="w"> </span><span class="s1">'9.0'</span>
<span class="w"> </span>
<span class="n">target</span><span class="w"> </span><span class="s1">'DispBBS'</span><span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="c1"># Comment this line if you're not using Swift and don't want to use dynamic frameworks</span>
<span class="w"> </span><span class="n">use_frameworks!</span>
<span class="w"> </span>
<span class="w"> </span><span class="c1"># Pods for DispBBS</span>
<span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'Alamofire'</span>
<span class="k">end</span>
</div>
移動輸入游標,在 # Pods for DispBBS 這行下面
按 i 切換到 insert 模式,輸入 pod 'Alamofire'
接著按 Esc 退出 insert 模式後,輸入「:x」即可存檔離開 vim
回到專案目錄下,輸入
$ pod install
就會開始安裝寫在 Podfile 裡的函式庫
安裝好之後會在專案的目錄下新增一個 DispBBS.xcworkspace 檔
之後都要改用 DispBBS.xcworkspace 來開啟專案
而不是原本的 DispBBS.xcodeproj
回到 Xcode,關閉原本的專案,使用 DispBBS.xcworkspace 開啟
可以看到專案資料夾變成這樣
<div class="img" data-ori_w="515" data-ori_h="539" style="width:258px;height:270px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/AVk2IMz.png" alt="[圖]" /></div>
Alamofire 已經加入專案中了
要使用 Alamofire 時在檔案前面加上
<div class="highlight"><span></span><span class="kd">import</span> <span class="nc">Alamofire</span>
</div>
如果出現錯誤訊息「cannot load underlying module for 'Alamofire'
點一下「Product」/「Clean」,再點「Build」就可以了
<div class="img" data-ori_w="379" data-ori_h="487" style="width:190px;height:244px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ryjginp.png" alt="[圖]" /></div>
<span style="color:#00F000">更新類別庫的版本</span>
在專案的目錄下執行
$ pod update
<span style="color:#00F000">查詢安全的類別庫版本</span>
打開專案目錄下的 Podfile.lock
這邊會記錄目前安裝的類別庫版本
以及 CocoaPods 版本
<span style="color:#00F000">指定安裝版本</span>
要指定安裝的類別庫板本,例如 Alamofire 4.4.0
修改 Podfile 中的 pod 'Alamofire' 為
<div class="highlight"><span></span><span class="w"> </span><span class="n">pod</span><span class="w"> </span><span class="s1">'Alamofire'</span><span class="p">,</span><span class="w"> </span><span class="s1">'~> 4.4.0'</span>
</div>
<span style="color:#00F000">移除類別庫</span>
將類別庫那一行加上# 或是刪除那行
然後再執行
$ pod update
參考
AppCoda <a href="http://www.appcoda.com.tw/cocoapods/" target="_blank" rel="nofollow">CocoaPods 簡介 : 如何輕鬆管理 Swift / Objective-C 的類庫</a>
□ 錯誤解決記錄
pod update 後出現
[!] Failed to connect to GitHub to update the CocoaPods/Specs specs repo
參考 <a href="https://stackoverflow.com/a/48962041/5759096" target="_blank" rel="nofollow">StackOverflow</a>
照上面的步驟更新 openssl, ruby, cocoapod
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-05 15:19:55</span>
<span class="record">※ 編輯: Knuckles 時間: 2018-08-21 00:31:17</span></div></pre>
Knuckles
[Xcode][Swift3] 使用 TableView 產生列表頁
http://disp.cc/b/11-9UkW
2017-03-01T08:15:48+08:00
2017-04-16T15:48:35+08:00
延續上一篇 [Xcode][Swift3] 使用 Swift3 開新專案 - KnucklesNote板 - Disp BBS
使用 Sigle View Application 建立一個新專案之後
我們想要將初始頁面改為一個 Table View
所以要刪掉預設的 View Controller,改成 Table View Controller
先點一下 storyboard 中的 View Controller 後按 Delete 刪除
再點一下相應的程式檔 ViewController.swift 後按 Delete,選「Move to Trash」刪除
回到 storyboard
從右下的 Object library 拉一個 Navigation Controller 進來
Navigation Controller 後面就會自動附帶一個 Table View Controller
Navigation Controller 後面加上的頁面會在上方產生一條固定住的導覽列 Navigation Bar
此時會冒出兩個警告訊息,點一下三角型的警告圖示
第一個警告是說 Table ...]]>
<pre><div style="color:#C0C0C0;background-color:#000; font-family:mingliu; white-space: pre-wrap;word-wrap: break-word; padding:0 .3em 0 .3em;"><div style="background-color:#000080;line-height:100%;"><div style="float:right;"><span style="color:#000080;background-color:#C0C0C0"> 看板 </span> KnucklesNote </div><span style="color:#000080;background-color:#C0C0C0"> 作者 </span> Knuckles (站長 那克斯)<br /><span style="color:#000080;background-color:#C0C0C0"> 標題 </span> [Xcode][Swift3] 使用 Table View 產生列表頁<br /><span style="color:#000080;background-color:#C0C0C0"> 時間 </span> 2017-03-01 Wed. 08:15:47</div><hr color="#008080" />───────────────────
延續上一篇 <a href="http://disp.cc/b/11-9Ufe" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Swift3 開新專案 - KnucklesNote板 - Disp BBS</a>
使用 Sigle View Application 建立一個新專案之後
我們想要將初始頁面改為一個 Table View
所以要刪掉預設的 View Controller,改成 Table View Controller
先點一下 storyboard 中的 View Controller 後按 Delete 刪除
再點一下相應的程式檔 ViewController.swift 後按 Delete,選「Move to Trash」刪除
<div class="img" data-ori_w="1042" data-ori_h="574" style="width:521px;height:287px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nvBxzNQ.png" alt="[圖]" /></div>
回到 storyboard
從右下的 Object library 拉一個 Navigation Controller 進來
<div class="img" data-ori_w="1246" data-ori_h="662" style="width:623px;height:331px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tdmNy0k.png" alt="[圖]" /></div>
Navigation Controller 後面就會自動附帶一個 Table View Controller
Navigation Controller 後面加上的頁面會在上方產生一條固定住的導覽列 Navigation Bar
此時會冒出兩個警告訊息,點一下三角型的警告圖示
<div class="img" data-ori_w="1234" data-ori_h="112" style="width:617px;height:56px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/m3azY8U.png" alt="[圖]" /></div>
第一個警告是說 Table View 中的 Cell 必需要設定 reuse identifier
點一下左邊 Document Outline 的「Table View Cell」
(或是點圖中的 Prototype Cells)
然後在右邊的 Attributes inspector (屬性檢視器)
在 Indentifier 輸入「HotTextCell」,之後會在程式裡用到
<div class="img" data-ori_w="1287" data-ori_h="537" style="width:644px;height:269px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/MaPHEEE.png" alt="[圖]" /></div>
第二個警告是說 Navigation Controller 沒有進入點
點一下 Navigation Controller,在右邊的屬性檢視器,
將「Is Initial View Controller」打勾
Navigation Controller 的左邊就會出現一個進入點的箭頭,
代表程式一開始會先從這個頁面開始顯示
<div class="img" data-ori_w="1276" data-ori_h="653" style="width:638px;height:327px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/674zvKi.png" alt="[圖]" /></div>
這樣兩個警告就消失了
接著修改 Table View 頁面上方的 Navigation Bar 要顯示的文字
因為 Table View Controller 是附屬在 Navigation Controller 之後
所以會有一個 Navigation item,在左邊的 Document Outline 點一下後
在右邊的屬性檢視器將 Title 改為「熱門文章」
Back Button 改為「回列表」
<div class="img" data-ori_w="1241" data-ori_h="516" style="width:621px;height:258px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/6sNP027.png" alt="[圖]" /></div>
Back Button 就是點選列表進到另一頁後,在左上角的按鈕要顯示的文字
<div class="img" data-ori_w="526" data-ori_h="92" style="width:263px;height:46px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/gOq37tf.png" alt="[圖]" /></div>
Table View 中每一列的元件叫做 Cell
要修改 Cell 的樣式,可以在屬性檢視器將 Style 改為內建的樣式「Subtitle」
<div class="img" data-ori_w="1282" data-ori_h="408" style="width:641px;height:204px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/UG2uaAV.png" alt="[圖]" /></div>
可以看到 Cell 中自動出現了兩個 Label
<span style="color:#00F000">在 Table View 中顯示動態資料</span>
要在 Table View 裡填入資料的話
要新增一個自訂的 Table View Controller 類別程式檔
點「File」/「New」/「File...」(或 command+n)
<div class="img" data-ori_w="1095" data-ori_h="364" style="width:548px;height:182px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/g5lCTEa.png" alt="[圖]" /></div>
選 iOS 的「Cocoa Touch Class」,點「Next」
<div class="img" data-ori_w="1142" data-ori_h="809" style="width:571px;height:405px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/tlkG2he.png" alt="[圖]" /></div>
Class 輸入類別名稱「HotTextViewController」
Subclass of 輸入要繼承的類別「UITableViewController」
語言選「Swift」,點「Next」
<div class="img" data-ori_w="944" data-ori_h="335" style="width:472px;height:168px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/iGtyJJm.png" alt="[圖]" /></div>
選擇檔案的儲存位置,預設就是專案的資料夾,點「Create」
就產生了一個繼承自 UITableViewController 的類別 HotTextViewController
<div class="img" data-ori_w="1216" data-ori_h="937" style="width:608px;height:469px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/A9R1t6W.png" alt="[圖]" /></div>
產生類別程式檔後要記得在 storyboard 設定自訂類別
到 storyboard 點一下 Table View Controller
在右邊的 Identity inspector 將 Class 設定為 HotTextViewController
<div class="img" data-ori_w="1304" data-ori_h="295" style="width:652px;height:148px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/b1p1yd3.png" alt="[圖]" /></div>
修改 HotTextViewController.swift
將自動產生的兩個成員函數改為
<div class="highlight"><span></span> <span class="c1">// </span><span class="cs">MARK:</span><span class="c1"> - Table view data source</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="c1">// #warning Incomplete implementation, return the number of sections</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="n">section</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span> <span class="p">-></span> <span class="nb">Int</span> <span class="p">{</span>
<span class="c1">// #warning Incomplete implementation, return the number of rows</span>
<span class="k">return</span> <span class="mi">5</span>
<span class="p">}</span>
</div>
第一個函數是設定 Table View 有幾個 Section,
將 return 0 改為 return 1 即可
第二個函數是設定每個 section 各有幾個 row
先測試看看,將 return 0 改為 return 5,直接設定為 5 個
將第一個被註解掉的函數取消註解,並改為
<div class="highlight"><span></span> <span class="c1">// 1.</span>
<span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="c1">// 2.</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"HotTextCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
<span class="c1">// Configure the cell...</span>
<span class="c1">// 3.</span>
<span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"這是第 </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s"> 列"</span>
<span class="n">cell</span><span class="p">.</span><span class="n">detailTextLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"測試文字"</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
1. 關於函數的輸入參數,有分為外部(External)參數名和內部(Local)參數名
例如第二個輸入參數為 cellForRowAt indexPath: IndexPath
cellForRowAt 是外部參數名,是在呼叫這個函數時要輸入的,
例如要取得第0列的cell可使用 cell = tableView(tableView, cellForRowAt: 0)
indexPath 是內部參數名,只能在函數定義的內容中使用
外部變數名使用底線 _ 時,代表呼叫時可以不用輸入外部參數名
2. 將 "reuseIdentifier" 改成之前在 storyboard 設定的 "HotTextCell"
這邊使用 dequeueReusableCell() 取得了一個可回收的 cell
為了避免列數太多時記憶體不夠用,所以 Table View 只會顯示目前滑動範圍內的 cell
滑動列表頁時會將滑出範圍外的 cell 回收,變成新出現的 cell 來使用
3. 設定 Cell 中兩個 Label 要顯示的文字
主標題的文字中使用 \(indexPath.row) 在字串中使用函數的局部參數名,
取得目前這是第幾個 row
關於 cell.textLabel?.text = "..." 中,textLabel 後面的問號
意思是當問號前的值是 nil 時,問號後的程式就不執行了,並傳出 nil 傳
用來避免 cell.textLabel 不存在時,卻呼叫了 .text 造成程式錯誤
執行看看
<div class="img" data-ori_w="598" data-ori_h="532" style="width:299px;height:266px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LCd1Ltm.png" alt="[圖]" /></div>
可以顯示 5 個 row,並顯示各自是第幾個 row
<span style="color:#00F000">使用自訂的 cell 樣式</span>
前面我們在 storyboard 將 cell 的樣式設為內建的 Subtitle
所以取得的 cell 會有兩個成員變數 textLabel 與 detailTextLabel 可以設定
如果想要使用自訂的成員變數的話,要改寫 Table View Cell 類別的程式檔
按 command+n 新增 Cocoa Touch Class
Class: HotTextCell
Subclas of: UITableViewCell
<div class="img" data-ori_w="902" data-ori_h="286" style="width:451px;height:143px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/QXo3JbW.png" alt="[圖]" /></div>
新增一個繼承自 UITableViewCell 的類別 HotTextCell
Note: 自訂類別的名稱應該取為「TableViewCell」比較好
因為這個自訂類別的功用只有加上成員變數而已,
之後加上其他的 TableView 時可以共用這個 Cell 類別
打開 HotTextCell.swift
在 class HotTextCell: UITableViewCell { 這行下面加上
<div class="highlight"><span></span> <span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">titleLabel</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">descLabel</span><span class="p">:</span> <span class="bp">UILabel</span><span class="p">!</span>
<span class="kr">@IBOutlet</span> <span class="kr">weak</span> <span class="kd">var</span> <span class="nv">thumbImageView</span><span class="p">:</span> <span class="bp">UIImageView</span><span class="p">!</span>
</div>
建立了三個成員變數 titleLabel, descLabel, thumbImageView
用來顯示兩個 UILabel 與一個 UIImageView
前面加上 @IBOutlet 代表這是要用來連結 storyboard 上面的元件用的
變數的型態後面加了問號或驚嘆號時,代表這個變數可以是 nil 值 (Optional)
使用驚嘆號代表變數被賦值後就不會再變成 nil 值了
到 storyboard 點一下 Table View Cell,
在 Identity inspector 設定 Class 為剛剛新增的程式檔 HotTextCell
<div class="img" data-ori_w="1234" data-ori_h="330" style="width:617px;height:165px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/yMXd4XH.png" alt="[圖]" /></div>
點一下 Table View 後在右邊的 Size inspector (尺寸檢視器)
將 Row Height 從 44 改為 100
<div class="img" data-ori_w="1302" data-ori_h="367" style="width:651px;height:184px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/QmJLypa.png" alt="[圖]" /></div>
可以看到 Cell 的高度變大了
注意在使用動態 Table View 時,設定 Cell 的高度沒有用
要設定 Table View 的高度才行
在 Table View Cell 的屬性檢視器,將 Style 改回 Custom
<div class="img" data-ori_w="1302" data-ori_h="316" style="width:651px;height:158px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/4MoLJ0F.png" alt="[圖]" /></div>
從右下角的 Object library 拉一個 ImageView 進來
在尺寸設定 X: 0, Y: 0, Width: 100, Height: 100
<div class="img" data-ori_w="1562" data-ori_h="888" style="width:781px;height:444px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/nMvmkc2.png" alt="[圖]" /></div>
屬性檢視器的 Content Mode 選「Aspect Fill」,
Drawing 的「Clip To Bounds」打勾,
這樣會將圖片等比例縮小到寬或高為100,然後裁掉超出範圍的部份
<div class="img" data-ori_w="963" data-ori_h="692" style="width:482px;height:346px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/1b18Kku.png" alt="[圖]" /></div>
再拉兩個 Label 進來
第一個 Label 的屬性檢視器
Text: Plain title, Color: 藍色, Font: System 16.0, Lines: 2
Autoshrink: Minimum Font Size 14 (文字太多時自動將文字縮小,最小至14)
尺寸設定 X: 108, Y: 5, Width: 259, Height 38
第二個 Label 的屬性檢視器
Text: Plain title, Color: 黑色, Font: System 14.0, Lines: 3
尺寸設定 X: 108, Y: 45, Width: 259, Height 52
<div class="img" data-ori_w="1246" data-ori_h="675" style="width:623px;height:338px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/QlONG5Q.png" alt="[圖]" /></div>
(顯示的可能會跟上面的圖不太一樣,因為設定值有再改過)
接著要將這三個元件連結之前在類別中新增的三個成員變數
點一下 Table View Cell,在右邊的 Connection inspector (連結檢視器)
可以在 Outlets 看到我們之前新增的三個成員變數
<div class="img" data-ori_w="1249" data-ori_h="386" style="width:625px;height:193px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/YWHjUrP.png" alt="[圖]" /></div>
將三個成員變數右邊的圈圈拉到圖中對應的位置
<div class="img" data-ori_w="1251" data-ori_h="438" style="width:626px;height:219px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/R8FX95x.png" alt="[圖]" /></div>
即完成類別的成員變數與 storyboard 元件的連結
加入一張要用來顯示的圖檔 <a href="http://i.disp.cc/disp/logo/displogo120.png" target="_blank" rel="nofollow">displogo120.png</a>
打開左邊的 Assets.xcassets,從 Finder 將圖片拉進來
<div class="img" data-ori_w="1274" data-ori_h="638" style="width:637px;height:319px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/icuciP6.png" alt="[圖]" /></div>
打開 HotTextViewController.swift
修改成員函數 tableView(_:cellForRowAt:)
<div class="highlight"><span></span> <span class="kr">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="kc">_</span> <span class="n">tableView</span><span class="p">:</span> <span class="bp">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="n">indexPath</span><span class="p">:</span> <span class="n">IndexPath</span><span class="p">)</span> <span class="p">-></span> <span class="bp">UITableViewCell</span> <span class="p">{</span>
<span class="c1">// 1.</span>
<span class="kd">let</span> <span class="nv">cell</span> <span class="p">=</span> <span class="n">tableView</span><span class="p">.</span><span class="n">dequeueReusableCell</span><span class="p">(</span><span class="n">withIdentifier</span><span class="p">:</span> <span class="s">"HotTextCell"</span><span class="p">,</span> <span class="k">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span> <span class="k">as</span><span class="p">!</span> <span class="n">HotTextCell</span>
<span class="c1">// Configure the cell...</span>
<span class="c1">// 2.</span>
<span class="n">cell</span><span class="p">.</span><span class="n">titleLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"這是第 </span><span class="si">\(</span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="si">)</span><span class="s"> 列"</span>
<span class="n">cell</span><span class="p">.</span><span class="n">descLabel</span><span class="p">?.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"測試文字 測試文字 測試文字 測試文字 測試文字 測試文字 測試文字 測試文字 測試文字 測試文字 "</span>
<span class="n">cell</span><span class="p">.</span><span class="n">thumbImageView</span><span class="p">?.</span><span class="n">image</span> <span class="p">=</span> <span class="bp">UIImage</span><span class="p">(</span><span class="n">named</span><span class="p">:</span> <span class="s">"displogo120"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</div>
1. 後面加上 as! HotTextCell 將取得的 TextViewCell 向下轉型為子類別 HotTextCell
加驚嘆號代表這是一個確定可以成功的轉型
2. 設定 HotTextCell 類別的三個成員變數
執行看看
<div class="img" data-ori_w="524" data-ori_h="816" style="width:262px;height:408px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/KlUGiHs.png" alt="[圖]" /></div>
圖片和文字可以正常顯示了,
但是在橫置,或是用其他尺寸的手機時版面不會自動調整
<div class="img" data-ori_w="932" data-ori_h="523" style="width:466px;height:262px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/Z5S7jDk.png" alt="[圖]" /></div>
<span style="color:#00F000">設定 Auto Layout</span>
打開 storyboard
點一下 Title Label 後,點右下角的「Add New Constraints」按鈕
<div class="img" data-ori_w="1137" data-ori_h="945" style="width:569px;height:473px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/jarkzVG.png" alt="[圖]" /></div>
點一下上、左、右三個方向的虛線,讓他變實線
<div class="img" data-ori_w="394" data-ori_h="257" style="width:197px;height:129px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/LGcmozb.png" alt="[圖]" /></div>
左方向的點下拉選單,選與 Content View 的間距,而不是 Image View
<div class="img" data-ori_w="488" data-ori_h="273" style="width:244px;height:137px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ulcC4Q9.png" alt="[圖]" /></div>
將 Height 打勾,然後點「Add 4 Constraints」
<div class="img" data-ori_w="414" data-ori_h="578" style="width:207px;height:289px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/raLtLQC.png" alt="[圖]" /></div>
加上這四個 Constraints 的意思就是說
我要固定住 Title Label 的上、左、右與 Content View 的間距
以及固定 Title Label 的高度,而寬度是不固定的
所以當 Content View 變寬時,Title Label 就會跟著變寬了
雖然 Content View 的高度是不會變的,
但是只要設定了左右的 Constraints,就會出現缺少上下 Constraints 的警告
所以四個方向都得要設定才行
接下來加上 Desc Label 的四個 Constraints
左邊一樣要選擇與 Content View 的間距,上方是與 Title Label 的間距
<div class="img" data-ori_w="1133" data-ori_h="898" style="width:567px;height:449px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/7DTbBwr.png" alt="[圖]" /></div>
如果 Constraints 設定錯誤了,可以點右下角的「Resolve Auto Layout Issues」
然後選 Selected Views 的「Clear Constraints」
就可以將 Constraints 清掉重新設定了
如果元件的尺寸設定和 Constraints 的設定不相符時,會出現橘色的線
可以點「Resolve Auto Layout Issues」的「Update Constraint Constants」
更新 Constraints 的設定值
或是點「Update Frames」按鈕,將元件的尺寸設定更新為 Constraints 的設定值
<div class="img" data-ori_w="570" data-ori_h="413" style="width:285px;height:207px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/ehQEaoO.png" alt="[圖]" /></div>
執行看看,在橫置的時候會自動符合寬度了
<div class="img" data-ori_w="933" data-ori_h="513" style="width:467px;height:257px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/mN88FKr.png" alt="[圖]" /></div>
<span style="color:#00F000">分隔線設定</span>
Table View 的分隔線預設左邊有15點的間距
要取消間距的話,在 Table View Cell 的屬性檢視器
Separator 選「Custom Insets」,Left 改為 0
<div class="img" data-ori_w="1304" data-ori_h="538" style="width:652px;height:269px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/wJOVJCG.png" alt="[圖]" /></div>
執行結果
<div class="img" data-ori_w="525" data-ori_h="573" style="width:263px;height:287px"><img style="max-width:100%;" data-ratio="2" src="http://i.imgur.com/CiNpPet.png" alt="[圖]" /></div>
要讀取網站內容顯示在列表中的話,參考下一篇
<a href="http://disp.cc/b/11-9UWG" target="_blank" rel="nofollow">[Xcode][Swift3] 使用 Alamofire 存取網站資料 - KnucklesNote板 - Disp BBS</a>
參考
<a href="https://www.raywenderlich.com/113388/storyboards-tutorial-in-ios-9-part-1" target="_blank" rel="nofollow">Ray Wenderlich: Storyboards Tutorial in iOS 9: Part 1</a>
<div style="clear:both"></div>--
<span class="record">※ 作者: Knuckles 時間: 2017-03-01 08:15:47</span>
<span class="record">※ 編輯: Knuckles 時間: 2017-04-16 15:48:34</span></div></pre>