Author Archives

raizan2ame

Swift4:サブスレッドで同期処理

iOSの同期処理の仕方をそもそもわかっていないこともあり、少し苦労しました。

しかし@shtnkgmさんに助けて頂きました。感謝です。参照したページはSwiftで複数の非同期処理の完了時に処理を行うです。

そもそも根本的につまづいた点は、main threadでないと同期がうまくいかない(暴走してしまう)という点です。これは2行追加すればいいと教えてもらい、すぐにやってみたのですが、ロジックはうまく通っているのですが、UIに結果が反映されません。バグだとわかっていても、すぐに検出できず、一晩寝て、今朝、1行削除し過ぎたことに気づきました。

やりたかったこと:

  • 「UITextViewの選択された文字列にリンクを入れる」とき、リンクを予め用意したUITextFieldに入れるのでなく、ポップアップ画面を出して入れるようにしたい。
  • そうすれば、UITextFieldを画面に用意しなくてすみます。

Screenshot 2018-05-24 19.38.46

Xcode9.3.1サンプルコードはここにあります。コードは基本的に@shtnkgmさんがアップされているものと同じです。

 

 

 

Swift4:UITextViewにリンクを設定

UITextViewの選択されたテキストにURLリンクを設定できるようにしました。

この実装でひとつだけ注意するべきことがありました。次のエラーが出てしまうのです。

@IBOutlet weak var yUserNote:UITextView!
@IBOutlet weak var yLink:UITextField!

yUserNote.linkTextAttributes    = [NSAttributedStringKey.foregroundColor.rawValue:  colorLiteral(red: 1, green: 0.2527923882, blue: 1, alpha: 1)]

Cannot assign value of type ‘[NSAttributedStringKey : UIColor]’ to type ‘[String : Any]!’

これはSwift4.0からのようです。
参考:Swift 4.0でNSAttributedStringの装飾属性Dictionaryがエラーになる件

結局、次のようにしたらこの文法エラーは消えました。

yUserNote.linkTextAttributes    = [NSAttributedStringKey.foregroundColor.rawValue:  colorLiteral(red: 1, green: 0.2527923882, blue: 1, alpha: 1)] as [String:Any]

Screenshot 2018-05-18 11.20.48

 

 

Swift4:文字列に色付(ソースコード)

UITextViewの任意の文字列をユーザーが選択し、色をつけることができる機能を実装しました。ソースコードはここ

文字数の問題:

最初、UITextViewの.textを選択された文字列、その前の文字列、その後の文字列に分けて抽出し、NSMutableAttributedStringで色付けしたものと合成する方法で実装を進めました。しかし、この方法だと、yUserNote.selectedRangeで入ってくる選択範囲に絵文字が含まれると、文字数ではなく、バイト数で入ってくることがわかりました(参考:文字列の長さを取得)。ソースコードの最後にあるCallGetStringByNSRangeを参考にしてください。

  • 重要:文字列に2バイトコードが含まれる場合、pString.utf16.count のような表現にしないと、yUserNote.attributedTextと生合成が保てない。

文字属性の設定の仕方:

そこで、抽出合成方法を放棄し、次の通り、UITextViewをNSMutableAttributedStringでUILabelに取り出し、選択文字列だけを色付けし、UILabelからUITextViewに戻す実装にしました。wAtt = yUserNote.attributedText as! NSMutableAttributedStringとすると、Xcodeのバグと思われるのですが、この命令で以上してしまうため、その迂回作としてUILabel➡︎UITextViewとしました。

  • 重要:UITextViewは直接NSMutableAttributedStringをハンドリングできないが、UILabelを仲介すれば、うまくいく。

import UIKit                                                        //
import WebKit                                                       //
class MesaNote: UIViewController, WKNavigationDelegate, WKUIDelegate {//
    @IBOutlet weak var  yUserNote:UITextView!                       //Note area
    var wRangeOrg:NSRange               = NSRange()                 //
    var wAtt:NSMutableAttributedString  = NSMutableAttributedString()//
    var wLabel:UILabel                  = UILabel()                 //
    override func viewDidLoad() {                                   //
        super.viewDidLoad()                                         //
        let wkString2:String        = “012345678900️⃣1️⃣abcde”        //
        yUserNote.attributedText    = NSAttributedString(string: wkString2,//
          attributes:[.foregroundColor :UIColor.blue, .font :UIFont(name: “Arial-BoldMT”, size:15)!])//
    }//============================================================//

    @IBAction func ExecIBAColor(){                                  //<*Add color to the selected text*>
        wRangeOrg                   = yUserNote.selectedRange       //
        wLabel.attributedText       = yUserNote.attributedText      //
        wAtt                        = wLabel.attributedText as! NSMutableAttributedString//
        wAtt.addAttributes([.foregroundColor : UIColor.red], range: wRangeOrg)//
        yUserNote.attributedText    = wAtt
    }//============================================================//

    override func didReceiveMemoryWarning() {                       //
        super.didReceiveMemoryWarning()                             //
        // Dispose of any resources that can be recreated.          //
    }//=============================================================//

 func CallGetStringByNSRange(_ pString:String, pRange:NSRange) -> String {//<*NSRange指定範囲の文字列を抽出する*>
        if pRange.location >= pString.utf16.count || pRange.location < 0
           || pRange.length <= 0 || pRange.location+pRange.length > pString.utf16.count { return “” }
        return String(pString[Range(pRange, in: pString)!])         //
    }//=============================================================//
}

Screenshot 2018-05-16 23.59.29

Swift4:UITableViewの高さ

UITableViewの高さ、行数、行の高さ・ヘッダーの高さ、先頭行位置、最終行位置などの操作についてのメモです。

この作表においては、.reloaData()を適当に入れたためこれが悪さし、各行の更新が終わらないようになり(ループしてしまい)、とても苦労しました。Z.bookArrayは[String]でbookmarkの配列になります。wkGyosuPerPageはInt、wkRowsizeとwkHeadersizeは常数(Int)です。

🔴 先頭行へのスクロールは単純です(Scroll to the top)。

    @IBAction func IBAExecGo2Top() {                                //スクロール先頭
        self.vTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)//
        wkIndexPath                 = IndexPath(row: 0, section: 0) //
    }//=============================================================//

🔴 最終行へのスクロールは1ページの行数と最終行位置で計算をする必要があります。
(Scroll to the bottom. Calculate the rows in the last page.)

wkGyosuPerPage = Int((vTableView.frame.height – wkHeadersize) / wkRowsize)

    @IBAction func IBAExecGo2Bottom() {                             //スクロール最後
        var wRow:Int                = Z.bookArray.count – 1         //最後の行位置
        if wRow < wkGyosuPerPage    { wRow   = 0 }                  //1ページ内に収まるとき、先頭行から表示。
        else                        { wRow   = wRow – (wkGyosuPerPage – 1) }//最終行ー(1ページ行数1)から表示。
        self.vTableView.scrollToRow(at: IndexPath(row: wRow, section: 0), at: .top, animated: false)//
        wkIndexPath                 = IndexPath(row:wRow, section:0)//
    }//=============================================================//

🔴 表全体の行数(The number of rows in all.)

    func tableView(_ tableView:UITableView, numberOfRowsInSection section:Int) -> Int {//表の行数を返す
        return Z.bookArray.count                                    //☎️♻️return♻️ bookmarkの行数
    }//=============================================================//☎️

🔴 表示(Edit the row.)

    func tableView(_ tableView:UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell {//表示
        var wCell:UITableViewCell                                   //☎️
        wCell = ExecSetValue(tableView, pIndex:indexPath)           //☎️表に値をsetする。wCellは編集結果。
        return wCell                                                //☎️♻️return♻️ infoArray cellを返す。
    }//=============================================================//☎️

🔴 クリック時の処理(The process for the selected row.)

    func tableView(_ tableView:UITableView, didSelectRowAt indexPath:IndexPath) {//クリックした行位置
        ExecClickedRow(indexPath.row, pCount:indexPath.count)       //☎️
        vTableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
    }//=============================================================//☎️

🔴 行の高さ(The height definition execpt the header.)

    func tableView(_ tableView:UITableView, heightForRowAt indexPath:IndexPath) -> CGFloat {//行の高さ
        return wkRowsize                                            //☎️
    }//=============================================================//☎️

🔴 ヘッダーの高さ(The height definition for the header.)

   func tableView(_ tableView:UITableView, heightForHeaderInSection section:Int) -> CGFloat {//ヘッダ高さ
       return wkHeadersize                                         //☎️
   }//=============================================================//☎️

🔴 ヘッダーの表示(Edit the header.)

    func tableView(_ tableView:UITableView, viewForHeaderInSection section:Int) -> UIView? {//ヘッダー設定
        let wHeader = ExecSetHeader(tableView, pSection:section)    //☎️ヘッダーを設定する。
        return wHeader                                              //☎️
    }//=============================================================//☎️

Screenshot 2018-05-13 07.41.26

Swift4:UIPickerView

必要に迫られ、ダイヤル方式のUI(UIPickerView)を実装しました。

使い方はtableViewと似ています。任意の初期値より、UIを設定するのに迷いました。

  • 一番迷ったところは、selectRowでした。何を勘違いしたのか、selectedRowになっていたため全く想定通りに動きませんでした。
  • もう一点は、UIPickerViewをstoryboardとプログラム変数と接続しないと、selectRowで値を任意の行(row)に設定できません。
  • アイコン(image)とテキストを合成して表示できます。アイコンはnilでも通ります。最初左揃えのテキストだけにしていたのですが、UIButtonに統一しました。色はstoryboardでも指定できます。
  • カラム(列)に分けることも可能です。

追記:書き換えた後、reloadしないと表示が更新されませんでした。
例) yPVLanguage.reloadAllComponents()

Screenshot 2018-05-08 14.28.49

Screenshot 2018-05-08 14.28.29

 

Swift4:iOSでのファイルハンドリング

iOSアプリでファイルの読み書きをしたいとき、アプリフォルダに入れるのが簡単です。

iCloudコンテナを使ってみたのですが、要領が悪いせいか、最新ファイルを確実に得ることができず苦労しました。その点、この方法は確実です。

info.plistにつぎのキーを追加し、YESにします。

Application supports iTunes file sharing
Supports opening documents in place

ファイルは次の場所にあります。ファイルアクセスは通常の方法で可能です。

Screenshot 2018-04-26 07.11.49

参考:iOS 11ファイルAppにDocumentsフォルダを表示して他のアプリと共有する方法

Swift4:ソートのNSStringを変更

NSStringが少し危ない件、最後に残っていたソートのNSStringも変更しました。

変更点は次のサンプルの赤い文字の部分です。

ついでにソートキーの変更はメッセージと同じ方式のモーダルで対処することで設定を簡便にしました。

😂 Before

let wCond:NSSortDescriptor = NSSortDescriptor(key:pIdentifier,
ascending:pSeq, selector: #selector(NSString.caseInsensitiveCompare(_:)))
let wResult = (Z.rcArray as NSArray).sortedArray(using: [wCond])//<– Swift 3.0

😊 After

func ExecSort(_ pIdentifier:String, pSeq:Bool) {
var wFile:String       = “” //Save the current row’s uniq key to find the row after sorting.
  if Z.maybeLastRow >= 0 { wFile = Z.rcArray[Z.maybeLastRow][Z.fFile] as! String }
else { }
  let wCond   = NSSortDescriptor(key:pIdentifier,ascending:pSeq)   
  let wResult = (Z.rcArray as NSArray).sortedArray(using: [wCond]) as NSArray 
Z.rcArray               = []
Z.rcArray               = wResult
  Z.maybeLastRow          = ExecGetCurrentRow(wFile)
yTableView?.reloadData()
}

😊 Reverse

func ExecSortReverse() {
var wFile:String         = “”
if Z.maybeLastRow >= 0 { wFile = Z.rcArray[Z.maybeLastRow][Z.fFile] as! String }
else { }
  Z.rcArray = Z.rcArray.reversed()
  Z.maybeLastRow           = ExecGetCurrentRow(wFile)
yTableView?.reloadData()
}

 

Swift4:NSStringの問題

時々、NSStringを使っている箇所でトラブルが起きるので、この際、代わりの方法を見つけ、改めることにしました。

その中でNSRangeだけが残ったので、次のように対応しました。

func CallGetStringByNSRange(_ pString:String, pRange:NSRange) -> String {
if pRange.location >= pString.count
|| pRange.location < 0
|| pRange.length <= 0 || pRange.location+pRange.length > pString.count { return “” }
    return String(pString[Range(pRange, in: pString)!])        
}