[Swift] 50 BBS閲覧アプリiOS版製作 その4 スレッド一覧表示 / kanna

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

選択した掲示板のスレッド一覧を表示させました。

掲示板のHTMLをパースして要素を取り出し、ChatGPTでC++の関数をSwiftへ変換して動作させました。FLTKに相当するViewについては自分で叩き台を考える必要があります。

Swiftの文法が分かっていればもっとスムーズに書けるはずですが、まあこれから学んでいきます。

次は選択スレッドの表示およびdatファイルの保存に関する実装に取り組みます。

プロジェクトの構成
import Foundation
import Kanna

struct Thread : Identifiable {
    let id = UUID()
    let threadID: String
    let threadTitle: String
    let threadNum: Int
}

func getThreads(url: String) async -> [Thread] { // urlはboard.url
    var threads: [Thread] = []
    var threadNumDict: [String: Int] = [:]
    var threadNum = 0
    
    let boardURL = url + "subback.html"
    guard let url = URL(string: boardURL) else { return [] }

    do {
        let htmlString = try String(contentsOf: url, encoding: .shiftJIS)
        guard let doc = try? HTML(html: htmlString, encoding: .shiftJIS) else { return [] }

        for link in doc.xpath("//*[name()='small']/*[name()='a']") {
            guard let id = link["href"], let title = link.text else { continue }
            
            print(id)
            
            let truncatedID = String(id.dropLast(4))
                
            let idRegex = try! NSRegularExpression(pattern: "^[0-9]+$")
            let idRange = NSRange(location: 0, length: truncatedID.utf16.count)
            let isNumericID = idRegex.firstMatch(in: truncatedID, options: [], range: idRange) != nil
            
            if !isNumericID {
                continue
            }

            if threadNumDict[truncatedID] == nil {
                threadNum += 1
                threadNumDict[truncatedID] = threadNum
            }

            threads.append(Thread(threadID: truncatedID, threadTitle: title, threadNum: threadNumDict[truncatedID]!))
            
            
        }
    } catch {
        print("Error: \(error)")
    }

    return threads
}
import SwiftUI

struct BoardView: View {
    @State private var boardCategories: [BoardCategory] = []

    var body: some View {
        NavigationView {
            VStack{
                Text("板一覧")
                List(boardCategories) { boardCategory in
                    DisclosureGroup(boardCategory.category) {
                        ForEach(boardCategory.boards) { board in
                            NavigationLink(destination: ThreadView(board: board)) {
                                ScrollView {
                                    Text(board.name)
                                }
                            }
                        }
                    }
                }
            }
            Spacer()
        }
        .padding()
        .task {
            boardCategories = await getBoards()
        }
    }
}

struct BoardView_Previews: PreviewProvider {
    static var previews: some View {
        BoardView()
    }
}
import SwiftUI

struct ThreadView: View {
    @State private var threads: [Thread] = []
    var board: Board?

    var body: some View {
        VStack {
            Text("スレッド一覧")
            List{
                ForEach(threads) { thread in
                    Text(thread.threadTitle)
                }
            }
            Spacer()
        }
        .padding()
        .task {
            if let board = board {
               threads = await getThreads(url: board.url)
            }
        }
    }
}

struct ThreadView_Previews: PreviewProvider {
    static var previews: some View {
        ThreadView()
    }
}