質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.49%

CoreData に保存した NSAttributedString 内の画像がアプリキル後に再表示されない【Xcode 12.1】

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 503

score 2

前提・実現したいこと

現在、SNSアプリを作成しています。そのため、CoreDataを使用しています。

NSAttributedString 中に画像を挿入して、タイムラインに投稿する仕組みをその一部に実装しました。

CoreData にその画像つき文章を保存して、それをタイムラインに表示する所までは上手くいっております。

ですが下記の画像のように、アプリのキル(ホームボタンを二回押してフリップして終了)をしてまたアプリを開くと、画像だけが表示されません。

この時でも、ちゃんと画像が表示されるようにしたいと考えています。

以下、質問をする上で必要と思われる情報を記載してゆきます。

CoreData の Attribute

Attribute の type を Transformable。Custom Class を NSAttributedString にする事で保存をしています。Attribute の名前はpostedAttrTextにしました。

画像の挿入

imagePickerが画像を取得し終えたときに、テキスト内に画像を入れて、そのテキストを textView の attributedText に代入するということをしています。もしかしたらこの時にimagePickerが拾った画像のデータ(?)と、アプリを消してまた表示した際のデータにくい違いがあるのではないかとも考えました。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image: UIImage = info[.originalImage] as? UIImage {
            //画像選択時のカーソル位置を取得
            var currentPosition: Int = 0
            if let selectedTextRange = textView.selectedTextRange {
                currentPosition = textView.offset(from: textView.beginningOfDocument, to: selectedTextRange.start)
            } else {
                currentPosition = textView.text.count
            }

            let fullString: NSMutableAttributedString = NSMutableAttributedString(string: "")
            let leftString: NSAttributedString = textView.attributedText.attributedSubstring(from: NSRange(location: 0, length: currentPosition))
            let paddingRow: NSAttributedString = NSAttributedString(string: "\n")
            let rightString: NSAttributedString = textView.attributedText.attributedSubstring(from: NSRange(location: currentPosition, length: textView.text.count-currentPosition))
            let textViewWidth: CGFloat = textView.frame.width
            let imageSize: CGSize = image.size
            let newImageSize: CGSize = CGSize(width: textViewWidth, height: textViewWidth * imageSize.height / imageSize.width)
            let imageAttachment: NSTextAttachment = NSTextAttachment(image: image.reSizeImage(reSize: newImageSize))//reSizeImage...UIImageのExtension
            let imageString: NSAttributedString = NSAttributedString(attachment: imageAttachment)

            //How to image alignment to center???

            //padding がないと、画像真っ黒になる。
            fullString.append(leftString)
            fullString.append(paddingRow)
            fullString.append(imageString)
            fullString.append(paddingRow)
            fullString.append(rightString)

            textView.attributedText = fullString
        }
        dismiss(animated: true, completion: nil)
}

CoreData への保存・読み込み

そのテキストのCoreDataへの保存・読み込みに関連する場面だけを切り取ってそれを簡略化すると、以下のようになります。ENTITLE は SecondTimeLineModelとしております。仮称として、保存する記述のあるクラスをSaveViewController、読み込みの記述があるクラスをReadViewControllerとしています。

class SaveViewController: UIViewController {

    //CoreDataへの保存の際に必要な記述1
    var secondTimeLineModel: [SecondTimeLineModel] = [SecondTimeLineModel]()
    var managedObjectcontext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    override func viewDidLoad() {
        super.viewDidLoad()

        //CoreDataへの保存の際に必要な記述2
        let dataCondition = NSFetchRequest<SecondTimeLineModel>(entityName: "SecondTimeLineModel")
        do{
            secondTimeLineModel = try managedObjectcontext.fetch(dataCondition)
        }catch{
            print("Error")
        }
    }

    //投稿ボタン押下時の処理(関係しない部分は省略)
    @objc private func post(_ sender: UIBarButtonItem) {

        let object = SecondTimeLineModel(context: managedObjectcontext)

        object.postedAttrText = postAttrText//postAttrText...投稿ボタン押下時のTextView.attributedText
        coreData.secondTimeLineModel.append(object)
        (UIApplication.shared.delegate as! AppDelegate).saveContext()
    }
}
class ReadViewController: UIViewController {

    //CoreDataへの保存の際に必要な記述1
    var secondTimeLineModel: [SecondTimeLineModel] = [SecondTimeLineModel]()
    var managedObjectcontext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    override func viewDidLoad() {
        super.viewDidLoad()

        //CoreDataへの保存の際に必要な記述2
        let dataCondition = NSFetchRequest<SecondTimeLineModel>(entityName: "SecondTimeLineModel")
        do{
            secondTimeLineModel = try managedObjectcontext.fetch(dataCondition)
        }catch{
            print("Error")
        }
    }
}

//問題に無関係と思われるものは省略(heightForRowAtなど)
extension SecondViewController: UITableViewDelegate, UITableViewDataSource {

    //postedAttrTextを受け取ってTableViewCellの中のTextViewに表示する
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell: TimeLineTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TimeLineTableViewCell

        let datas = [
            "postedAttrText" : secondTimeLineModel[indexPath.row].postedAttrText!
        ] as [String : Any]

        //setUpTableViewCell...セルをセットアップするメソッド。この中の TextView の attributedText に postedAttrText を代入します。
        cell.setUpTableViewCell(index: indexPath, datas: datas)

        return cell
    }
}

試したこと

(追記:なぜか既に書いたはずのこの項目が消えてた・・・)

・iPhone, Xcode, Macの再起動・アップデート確認
・Use Core Dataでプロジェクトを作り直す
・英語でも日本語でも何時間か調べ尽くしたが、NSAttributedStringのCoreDataへの保存においてなぜか保存の事だけで読み込みに関する記事はなかった。
print(postedAttrText)を実行することで、文章中にしっかりとUIImageが入っていることを確認
・一方で、シミュレータ(実機)上で文章を選択するとそこには何もなく、コピー&ペーストしても空白しかない

補足情報(FW/ツールのバージョンなど)

macOS Catalina 10.15.7
Xcode 12.1

回答よろしくお願いいたします

私情により時間がなく、他のコーディングを進めているうちにteratailでの回答待ちをするという形をとらせて頂きましたが、もしこのまま回答が得られなかった場合はこの問題を抽象化した簡単なプロジェクトを作成してみて、解決したら自己解決として終了。そうでなくても何か新たな事実が判明すれば追記していきたいと思います。
何かご存知の方は、是非回答をよろしくお願いしますm(_ _)m

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

確認は取ってませんが、2019年1月29日時点では画像付きのNSAttributedStringをTransformableとしてCoreDataに保存すると画像が捨てられるバグがあるようです

https://www.amerhukic.com/storing-nsattributedstring-with-uiimage-in-core-data

ワークアラウンドも書かれてるのでそれで対処してみてはどうでしょうか?

(2019年1月29日時点での話なので状況が変わっている可能性があります)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/10/27 19:27

    拝見させて頂きました。良記事提供ありがとうございます!
    HTML使えばいけるんですね・・・! そして実は私は既にメモリの観点からCoreDataの使用を断念していて、結局Firebaseを使って作り直すことにしました。驚いたのは、CoreDataを用いた場合だとタイムラインにほんの十個か二十個ほどの投稿をするだけで600MBくらいiPhoneのストレージ食っちゃったんですよね。Twitterとかあれだけ流れてて150MBとかなのにです。多分上級者からすれば当たり前の事なのでしょうが、やはりCoreDataをやめてよかったと思っています。

    ただ、仕組み的にFirebaseでも同じようなことが起きると思いますので、HTMLを用いたこの記事の方法を試してみます。回答ありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る