SwiftPresso

SwiftPresso 将您的 WordPress 网站和您的 Swift 应用程序连接起来。

只需一行代码,您就可以将您的 WordPress 网站转换为 iOS 应用程序。 SwiftPresso 提供可定制的 SwiftUI 视图来显示您的网站内容。

如果您更喜欢完全控制,您可以创建自己的实体,并仅使用 SwiftPresso 来管理网络和域模型。

您还可以使用 SwiftPresso HTML 映射器将 HTML 文本转换为 NSAttributedString

关于 SwiftPresso 的文章:https://livsycode.com/swift/connect-your-ios-app-to-wordpress-wit-swiftpresso/

安装

SPM

.package(url: "https://github.com/Livsy90/SwiftPresso.git", from: "2.0.0")

内置视图

您可以调用 SwiftPresso 工厂方法来使用内置的帖子列表视图并注入所需的配置。

@main
struct DemoApp: App {
        
    var body: some Scene {
        WindowGroup {
            SwiftPresso.View.postList(host: "livsycode.com")
        }
    }
}

通过此配置,您可以确定 API 主机值、HTTP 方法和各种 UI 参数。

SwiftPresso.View.postList(
    host: "livsycode.com",
    backgroundColor: .blue,
    accentColor: .white,
    textColor: .white
)

有两种显示帖子内容的方式

要在这些选项之间进行选择,您可以设置配置值。

SwiftPresso.View.postList(host: "livsycode.com", isShowContentInWebView: true)

重要提示

如果您更喜欢第一种选项,请记住编辑您的 Info.plist 文件,以添加关于照片库使用权限的字符串,因为 SwiftPresso 允许用户将图像保存到相机胶卷。选择“Privacy - Photo Library Additions Usage Description”以添加此字符串。

固定链接

如果您的帖子中存在指向其他帖子的链接,您可以直接在应用程序中打开它们。您可以通过更改 WordPress 管理设置中的固定链接结构来启用此功能。为此,请转到 WordPress 管理面板并打开设置 -> 固定链接。应用程序需要在 URL 末尾提供帖子 ID 信息,以了解链接指向哪个帖子。因此,您可以指定例如这样的方案

/%category%/%postname%/%post_id%/

默认情况下,使用 WebView 提供了此选项。但是,对于点击指向标签或类别的链接以返回到已按此标签或类别过滤的帖子列表,您需要更改固定链接以包含标签和类别路径组件。例如,像这样。

/%category%/%postname%/

或者像上面的例子一样

/%category%/%postname%/%post_id%/

您也可以自定义标签和类别组件。

SwiftPresso.View.postList(
    host: "livsycode.com",
    tagPathComponent: "series",
    categoryPathComponent: "topics"
)

重要提示

更改固定链接会影响您的 SEO 设置,因此请谨慎操作。

加载指示器

虽然默认实现包含 shimmer 加载指示器,它将在加载帖子列表和单个帖子的数据时使用,但您也可以使用第二种方法来提供自定义指示器。

@main
struct DemoApp: App {
        
    var body: some Scene {
        WindowGroup {
            SwiftPresso.View.postList("livsycode.com") {
                ProgressView()
            }
        }
    }
}

配置

如果您使用 SwiftPresso 内置视图,则无需担心配置方法的调用。但是,当单独使用 SwiftPresso 实体时,您必须手动配置数据。为此,您可以使用 configure 方法。

    /// - Parameters:
    ///   - host: API host.
    ///   - httpScheme: API HTTP scheme.
    ///   - postsPerPage: Posts per page in the post list request.
    ///   - tagPathComponent: API Tag path component.
    ///   - categoryPathComponent: API Category path component.
    ///   - isShowContentInWebView: Using WKWebView as post view.
    ///   - isShowFeaturedImage: Post featured image visibility.
    ///   - backgroundColor: Post list and post view background color.
    ///   - accentColor: Post list and post view interface color.
    ///   - textColor: Post list and post view text color.
    ///   - postListFont: Post list font.
    ///   - postBodyFont: Post body font.
    ///   - postTitleFont: Post title font.
    ///   - menuBackgroundColor: Menu background color.
    ///   - menuTextColor: Menu text color.
    ///   - homeIcon: The icon for the navigation bar button that restores the interface to its default state.
    ///   - homeTitle: Navigation title for default state.
    ///   - searchTitle: Navigation title for search state.
    ///   - isShowPageMenu: Determines the visibility of the page menu.
    ///   - isShowTagMenu: Determines the visibility of the tag menu.
    ///   - isShowCategoryMenu: Determines the visibility of the category menu.
    ///   - pageMenuTitle: Determines the title of the page menu.
    ///   - tagMenuTitle: Determines the title of the tag menu.
    ///   - categoryMenuTitle: Determines the title of the category menu.
    ///   - isParseHTMLWithYouTubePreviews: If an HTML text contains a link to a YouTube video, it will be displayed as a preview of that video with an active link.
    ///   - isExcludeWebHeaderAndFooter: Remove web page's header and footer.
    ///   - isMenuExpanded: To expand menu items by default.
    public static func configure(
        host: String,
        httpScheme: HTTPScheme = .https,
        postsPerPage: Int = 50,
        tagPathComponent: String = "tag",
        categoryPathComponent: String = "category",
        isShowContentInWebView: Bool = false,
        isShowFeaturedImage: Bool = true,
        postListFont: Font = .title2,
        postBodyFont: UIFont = .systemFont(ofSize: 17),
        postTitleFont: Font = .largeTitle,
        backgroundColor: Color = Color(uiColor: .systemBackground),
        accentColor: Color = .primary,
        textColor: Color = .primary,
        menuBackgroundColor: Color = .primary,
        menuTextColor: Color = Color(uiColor: .systemBackground),
        homeIcon: Image = Image(systemName: "house"),
        homeTitle: String = "Home",
        searchTitle: String = "Search",
        isShowPageMenu: Bool = true,
        isShowTagMenu: Bool = true,
        isShowCategoryMenu: Bool = true,
        pageMenuTitle: String = "Pages",
        tagMenuTitle: String = "Tags",
        categoryMenuTitle: String = "Category",
        isParseHTMLWithYouTubePreviews: Bool = true,
        isExcludeWebHeaderAndFooter: Bool = true,
        isMenuExpanded: Bool = true
    ) { ... }
SwiftPresso.configure(host: "livsycode.com", httpScheme: .https)

SwiftPresso.Configuration 存储用于管理 API 请求和处理 UI 外观的值。

let host = SwiftPresso.Configuration.API.host
let color = SwiftPresso.Configuration.UI.backgroundColor
public enum Configuration {
    
    public enum API {
        
        /// API host.
        public static var host: String { get }
        
        /// Posts per page in the post list request.
        public static var postsPerPage: Int { get }
        
        /// API HTTP scheme.
        public static var httpScheme: HTTPScheme { get }
        
        /// API Tag path component.
        public static var tagPathComponent: String { get }
        
        /// API Category path component.
        public static var categoryPathComponent: String { get }
        
    }
    
    public enum UI {
        
        /// Remove web page's header and footer.
        public static var isExcludeWebHeaderAndFooter: Bool { get }
        
        /// Post list and post view background color.
        public static var backgroundColor: Color { get }
        
        /// Post list font.
        public static var postListFont: Font { get }
        
        /// Post body font.
        public static var postBodyFont: UIFont { get }
        
        /// Post title font.
        public static var postTitleFont: Font { get }
        
        /// Post list and post view interface color.
        public static var accentColor: Color { get }
        
        /// Post list and post view text color.
        public static var textColor: Color { get }
        
        /// Determines the visibility of the page menu.
        public static var isShowPageMenu: Bool { get }
        
        /// Post featured image visibility.
        public static var isShowFeaturedImage: Bool { get }
        
        /// Determines the visibility of the tag menu.
        public static var isShowTagMenu: Bool { get }
        
        /// Determines the visibility of the category menu.
        public static var isShowCategoryMenu: Bool { get }
        
        /// The icon for the navigation bar button that restores the interface to its default state.
        public static var homeIcon: Image { get }
        
        /// Determines the title of the page menu.
        public static var pageMenuTitle: String { get }
        
        /// Determines the title of the tag menu.
        public static var tagMenuTitle: String { get }
        
        /// Determines the title of the category menu.
        public static var categoryMenuTitle: String { get }
        
        /// Navigation title for default state.
        public static var homeTitle: String { get }
        
        /// Navigation title for search state.
        public static var searchTitle: String { get }
        
        /// Menu background color.
        public static var menuBackgroundColor: Color { get }
        
        /// Menu text color.
        public static var menuTextColor: Color { get }
        
        /// To expand menu items by default.
        public static var isMenuExpanded: Bool { get }
        
        /// Using WKWebView as post view.
        public static var isShowContentInWebView: Bool { get }
        
        /// If an HTML text contains a link to a YouTube video, it will be displayed as a preview of that video with an active link.
        public static var isParseHTMLWithYouTubePreviews: Bool { get }
        
    }
    
}

数据提供者

要使用 SwiftPresso 提供者,您可以使用 SwiftPresso 工厂方法。

let postListProvider = SwiftPresso.Provider.postListProvider()

SwiftPresso 中有六个提供者

帖子列表提供者

帖子列表提供者有两种方法来检索帖子模型数组,它们返回 SwiftPresso 中两种不同类型的帖子模型。

您可以配置多个参数来执行请求

class ViewModel {
    
    var postListProvider: some PostListProviderProtocol
    
    func getData() async {
        let data = try? await postListProvider.getPosts(
            pageNumber: 1,
            perPage: 20,
            searchTerms: "some text...",
            categories: [1,2,3],
            tags: [1,2,3],
            includeIDs: [1,22]
        )
        
        let rawData = try? await postListProvider.getRawPosts(
            pageNumber: 1,
            perPage: 20,
            searchTerms: "some text...",
            categories: [1,2,3],
            tags: [1,2,3],
            includeIDs: [1,22]
        )
    }
    
}

单个帖子提供者

单个帖子提供者有两种方法来检索特定的帖子模型。

class ViewModel {
    
    var postProvider: some PostProviderProtocol
        
    func getData() async {
        let data = try? await postProvider.getPost(id: 55)
        let rawData = try? await postProvider.getRawPost(id: 55)
    }
    
}

页面列表提供者

与帖子列表提供者一样,页面列表提供者也有两种方法来检索页面模型数组,它们返回 SwiftPresso 中两种类型的页面模型。

class ViewModel {
    
    var pageListProvider: some PageListProviderProtocol
        
    func getData() async {
        let data = try? await pageListProvider.getPages()
        let rawData = try? await pageListProvider.getRawPages()
    }
    
}

单个页面提供者

页面提供者也有两种方法来检索页面模型。

class ViewModel {
    
    var pageProvider: some PageProviderProtocol
        
    func getData() async {
        let data = try? await pageProvider.getPage(id: 22)
        let rawData = try? await pageProvider.getRawPage(id: 22)
    }
    
}

分类目录/标签列表提供者

分类目录和标签列表提供者提供一个模型数组,它是一个 Category 类型。

class ViewModel {
    
    var categoryListProvider: some CategoryListProviderProtocol
    
    func getData() async {
        let data = try? await categoryListProvider.getCategories()
    }
    
}
class ViewModel {
    
    var tagListProvider: some TagListProviderProtocol
        
    func getData() async {
        let data = try? await tagListProvider.getTags()
    }
    
}

HTML 映射器

HTML 映射器可以将 HTML 文本转换为 AttributedString。如果 HTML 文本包含指向 YouTube 视频的链接,它将显示为该视频的预览,并带有可点击的链接。

let mapper = SwiftPresso.Mapper.htmlMapper()

func map(post: PostModel) -> NSAttributedString {
    mapper.attributedStringFrom(
        htmlText: post.content,
        color: .black,
        fontSize: .systemFont(ofSize: 17).pointSize,
        width: 375,
        isHandleYouTubeVideos: true
    )
}

截图

帖子列表

帖子

菜单

要求