[Swift] 47 BBS閲覧アプリiOS版製作 その1 CocoaPods / Kanna

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

BBS閲覧アプリC++版をiOSへ移植します。

HTMLパーサーとしてKannaを使います。パッケージ管理システム CocoaPodsを使って導入しました。CocoaPodsはHomebrewでインストールしています。

悪戦苦闘しつつようやくビルドエラーにならない状態まで持ってこれましたが、この段階でApple純正のパッケージ管理システム Swift Package Manager(SPM)の存在を知りました。

次回、SPMでKannaを入れ直します。愚痴ってもしょうがないですが、Xcodeを使ったコーディングは色々とめんどくさいです。ライブラリ一つ導入するまでの時間的学習コストが高すぎます。ネット情報は古い陳腐化したものが優勢で油断するとミスリードされてしまいます。古い情報なのに最近の日付で更新となっているからタチが悪い。慣れているのもありますが、C++の方がのびのびと書けてストレスが少ないです。

掲示板リストはまだ表示できない

[Swift] 46 メモアプリ製作 その13 背景色のグラジエント

[Mac M2 Pro 12CPU, Ventura 13.6, watchOS 10.0.1, Xcode 15.0]

Apple Watchは表示面が小さくメモによっては全体を一度に表示できません。表示されているところがメモ全体のどこに当たるのか分かるようにするため、背景色をグラジエントにしました。

これでメモの下部を表示していても背景色が薄いので上部が隠れていると判断でき、メモの見落としを防ぐことができます。

スクロールバーでも同様の効果がありますが、watchOSでは常時表示にはできないため役に立ちません。

改良前:メモ1を見落とすおそれあり(実際は付番していないため)
改良後:メモ上部が隠れていることが分かる
.background(LinearGradient(gradient: Gradient(colors: 
[Color.blue, Color.blue.opacity(0.0)]), startPoint: .top, endPoint: .bottom))
struct ContentView: View {
    @Environment(\.managedObjectContext)var viewContext

    @FetchRequest(
    entity: Note.entity(),
    sortDescriptors: [NSSortDescriptor(key: "creationDate", ascending: false)])
    private var contents: FetchedResults<Note>

    var body: some View {
        NavigationView{
            List{
                ForEach(contents){content in
                    NavigationLink{
                        if((content.content?.isEmpty) == false){
                            DraftAppleWatch(text:content.content!, note: content)
                        }
                    }label:{
                        if((content.content?.isEmpty) == false){
                            Text(content.content!)
                            .font(.system(size: 20))
                            .background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.0)]), startPoint: .top, endPoint: .bottom))
                        }
                    }
                }
                .onDelete(perform:deleteContent)
            }
            .navigationTitle("メモリスト")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar{
                ToolbarItem(placement:.bottomBar){
                     NavigationLink{
                        DraftAppleWatch()
                    }label:{
                        Text("+")
                        .font(.system(size: 24))
                    }
                }
            }
        }
        .accentColor(.blue)
    }
<以下略>
ScrollView {
       Text(content.content!)
       .font(.system(size: 20))
       .background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.0)]), startPoint: .top, endPoint: .bottom))
}
スクロールしないと
右上のバーは表示されない

[Swift] 45 Apple WatchのComplication改良 コーナー表示で効かない修飾子

[Mac M2 Pro 12CPU, Ventura 13.6, watchOS 10.0.1, Xcode 15.0]

自製カレンダーのコーナーComplicationを改良しました。日付と元号の位置を入れ替えています。日付を本体として配置すると曜日がはみ出るため、ラベルの方に移動させました。

最初は日付と曜日が本体として収まるようフォント指定あるいは自動調整しようとしたものの、関連する修飾子はどれも効きませんでした。

また右上の天気に色を付けたいのですが、類似アプリを探すか自分で作るしかなさそうです。自製するなら天気APIを調査するところから着手でしょうか。

右下コーナー:曜日がはみ出る
曜日はラベルへ移動
struct ComplicationCorner : View {
    var entry: Provider.Entry
    
    var body: some View {
        Text(getFormattedDate())
//        Text(getFormattedDate() + getFormattedWeekday()) // 曜日は収まらないのでラベルへ
        .foregroundColor(.green)
        .minimumScaleFactor(0.5) // 効かない
        .font(.system(size: 60)) // 効かない
        .lineLimit(nil)          // 効かない
        .widgetCurvesContent()
        .widgetLabel {
            Text(getFormattedYear() + " " + getFormattedWeekday())
            .foregroundColor(.yellow)
            .font(.system(size: 20))
        }
        .containerBackground(for: .widget){
            Color.blue
        }
     }
<以下略>

[C++] 353 Pythonスクリプトの実行(再) / Rustの現状

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

以前にも扱いましたが、おさらいしておきます。

Rustでも簡単にPythonスクリプトを実行できます。ただRustは安全性が担保されない限りコーディングがなかなか先に進まないので、趣味としては楽しめないです。Javaのぬるぽ地獄に似たような窮屈さを感じます。

昨年2022年あたりがピークだったのか、今現在Rustブームは落ち着いているようです。プロダクト責任者に近い上級プログラマだけが熱を上げている印象です。Pythonのようなお手軽さが全くない言語ですから、ユーザーの新規参入が先細りにならないか心配です。

私としては、もしビジネスが絡んだら仕方なく使うというスタンスです。

#define PY_SSIZE_T_CLEAN
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

void sheetNaming() {
    Py_Initialize();

    // pyファイルの指定(OutingCosts.py)
    PyObject* myModuleString = PyUnicode_FromString((char*)"OutingCosts");

    // pyファイルのモジュール化
    PyObject* myModule = PyImport_Import(myModuleString);

    // pyファイル内の関数を指定
    PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"outingcosts");

    // 関数の引数を設定
    PyObject* args = NULL;
    // 引数2つの場合
    // PyObject* args = PyTuple_Pack(2,PyUnicode_FromString(path),PyUnicode_FromString(path2)))

    // 関数を実行
    PyObject* myResult = PyObject_CallObject(myFunction, args);

    Py_Finalize();

    return ;

}

int main() {
    sheetNaming();

    return 0;
}
# コンパイラ設定
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = -std=c++20
LDFLAGS = -lc++

# includeパス(-I)
INCLUDE = -I./include \
-I/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10

# ライブラリ(-l)
LIBRARY_l = -lpython3.10

# ライブラリパス(-L)
LIBRARY_L = -L/usr/local/lib -L/Library/Frameworks/Python.framework/Versions/3.10/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

# 実行ファイル
TARGETDIR = ./bin
TARGET = test

# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)

# コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET) $(TARGET).app
# 導入するPythonスクリプトのあるディレクトリ設定
export PYTHONPATH="/code/Python/library/python_module"

# importする自製ライブラリのあるディレクトリ設定
export PYTHONPATH="/code/Python/library"
RustでのPythonスクリプト実行例
Objective-CでのPythonスクリプト実行例

[Python] 361 Excelセルの日付をシート名にする openpyxl

[Mac M2 Pro 12CPU, Ventura 13.6, Python 3.10.4]

Excelシートの1枚目A2セルにある日付をyymmdd形式に変換し、シート名にするスクリプトです。

ChatGPTのAPIが不調のため、以前のようにネット検索で調べました。やはりネット検索スキルはAI時代でも必須ですね。

import openpyxl
from openpyxl import load_workbook
from datetime import datetime, timedelta

def sheet_naming():
      import warnings
      warnings.simplefilter(action='ignore', category=UserWarning)

      # Excelファイルを読み込む
      file = 'test.xlsx'
      bk = openpyxl.load_workbook(file)

      # 先頭シートを取得 
      sheet = bk.worksheets[0]
      # 日付セルA2の値(5桁整数,表示はmm/dd)を取得
      date0 = sheet['A2'].value

      # date0をyymmdd形式に変換する
      date = datetime(1899, 12, 30) + timedelta(days=date0)
      date_str = date.strftime('%y%m%d')
      print("date_str: " + date_str)

      # シート名を変更する
      sheet.title = date_str
      # 変更を保存する
      bk.save(file)

[C++] 351 BBS閲覧アプリの製作 その32 掲示板クリック時に落ちる / クラッシュレポートの閲覧

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

昨晩、自製BBS閲覧アプリで特定の掲示板をクリックするとアプリが落ちるようになってしまいました。

クラッシュレポートからXPath式を評価する際にエラーになることが判明しました。エラーになってもアプリが落ちないようにexecuteXpath関数の戻り値をNULL以外に変えるなど対処しましたが、結局うまくいきませんでした。

今朝になって正常動作するようになったものの、再発が懸念されます。クラッシュレポートは後からでもmacOSのコンソールアプリから閲覧できます。一応、エラーが発生しそうな箇所を修正して予防対策しておきました。戻り値云々は見当違いのようです。

当時の他の類似アプリでの挙動は確認できていませんが、ちょっとしたトラブルでもアプリが絶対に落ちないよう堅牢性を持たせたいものです。

// XPath式で要素を取得
xmlNodeSetPtr executeXpath(xmlDocPtr &doc, xmlChar *xpath_expr) {
    xmlXPathContextPtr xpath_context;
    xmlXPathObjectPtr  xpath_obj;

    xpath_context = xmlXPathNewContext(doc);
    if (xpath_context == NULL) {
        cerr << "Error: unable to create new XPath context" << endl;
        xmlFreeDoc(doc);
        return NULL;
    }
    xmlNodePtr node = xmlDocGetRootElement(doc);

    // XPath式を評価
    // 23/10/16 エラー発生, 23/10/17 回復
    xpath_obj = xmlXPathEvalExpression(xpath_expr, xpath_context);
    if (xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)) {
        cerr << "Error: unable to evaluate xpath expression" << endl;
        xmlXPathFreeContext(xpath_context);
        xmlFreeDoc(doc);
        return NULL;
    }

    return xpath_obj->nodesetval;
}
    xmlDoc* doc = htmlReadMemory(htmlBuffer.c_str(), htmlBuffer.size(), nullptr, nullptr, HTML_PARSE_RECOVER);
    if (doc) {
        xmlNodeSetPtr tradObj = executeXpath(doc, (xmlChar *)"//*[name()='small']/*[name()='a']");

        if (tradObj) {
            for (int i = 0; i < tradObj->nodeNr; i++) {
                xmlNodePtr node = tradObj->nodeTab[i];
                xmlChar* id0 = xmlGetProp(node, (xmlChar*)"href");
                xmlChar* title0 = xmlNodeGetContent(node);

                string id = convertToString(id0);
                id.erase(id.size() - 4);
                string title = convertToString(title0);
                // cout << "title: " << title << endl;

                idTitle.push_back(std::make_pair(id, title));
                
                xmlFree(id0);
                xmlFree(title0);
            }
        } else {
            return {}; // else部分がなかったので追記 23/10/17
        }
        xmlFreeDoc(doc);
    } else {
        return {}; // else部分がなかったので追記 23/10/17
    }
クラッシュレポートは後からでもコンソールアプリで確認可能
(保存期間は不明)

[C++] 350 ChatGPTアプリの製作 その37 libcrypto.libが見つからず起動しない

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 14.0.3]

今朝、自製ChatGPTアプリを起動しようとすると途中で落ちてしまうようになりました。

Homebrewのopensslライブラリを更新すると直りました。エラーレポートにあるlibcrypto.libは暗号化に関連するlibファイルです。

余談ですが、新macOS Sonomaは2023/10/11現在まだ初期バージョン14.0のままなので次のバージョンになったらサブ機に入れてみる予定です。

brew update & brew upgrade & brew install openssl
自製ChatGPTアプリの最新UI

参考サイト
Apple Developer Forums

[Python] 360 PDFファイルを結合

[Mac M2 Pro 12CPU, Ventura 13.6, Python 3.10.4]

PDFを色々加工するPythonスクリプトがたまってきました。

C++に移植してGUIアプリにまとめようかと考えています。

最近ファイル名やファイルパスを加工するのにosモジュールをよく使います。これまではsplitメソッドなどを使った文字列加工を多用していましたが、osモジュールのメソッドの方がさすがに使いやすいですね。

import os
from PyPDF2 import PdfMerger

# PDFファイルのディレクトリ
pdf_folder = '/images/'

# PDFファイルのリストを作成
pdf_files = [os.path.join(pdf_folder, f) for f in os.listdir(pdf_folder) if f.endswith('.pdf')]
pdf_files.sort()

# 先頭PDFファイル名を元に結合ファイル名を作成
pdf_name = os.path.splitext(pdf_files[0])[0] + "_join" + os.path.splitext(pdf_files[0])[1]
pdf_path = os.path.join(pdf_folder, pdf_name)

# PDFファイルを結合
merger = PdfMerger()
for pdf_file in pdf_files:
    merger.append(pdf_file)
merger.write(pdf_name)
merger.close()