From cceeed51a0331bfc4ed80cc043245462e7a10a18 Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 1 Jul 2026 11:40:07 +0800 Subject: [PATCH 1/2] Add new styling options and header item management to STMarkdown components - Introduced new properties in STMarkdownStyle for table header bar background color, minimum row height, font, header items, button width, and corner mask. - Updated STMarkdownTableCell to apply custom font from style during configuration. - Added STMarkdownTableHeaderItem struct for customizable header buttons and integrated it into STMarkdownTableView. - Enhanced header item management in STMarkdownTableView to support dynamic button creation and styling. --- Sources/STMarkdown/Core/STMarkdownStyle.swift | 25 +++ .../Table/STMarkdownTableCell.swift | 15 +- .../Table/STMarkdownTableView.swift | 195 ++++++++++++------ 3 files changed, 176 insertions(+), 59 deletions(-) diff --git a/Sources/STMarkdown/Core/STMarkdownStyle.swift b/Sources/STMarkdown/Core/STMarkdownStyle.swift index a208ad7..f6873cd 100644 --- a/Sources/STMarkdown/Core/STMarkdownStyle.swift +++ b/Sources/STMarkdown/Core/STMarkdownStyle.swift @@ -54,6 +54,18 @@ public struct STMarkdownStyle: @unchecked Sendable { public var tableHeaderTextColor: UIColor? public var tableBorderColor: UIColor? public var tableBackgroundColor: UIColor? + /// 顶部工具条背景色(nil 时回退到 tableBackgroundColor → secondarySystemBackground) + public var tableHeaderBarBackgroundColor: UIColor? + /// 表格行最小高度,同步影响 UICollectionView 布局与 computeSize + public var tableMinimumRowHeight: CGFloat + /// 单元格字体,非 nil 时在 configure 阶段覆盖 attributedContent 中的字体大小(保留粗/斜等修饰) + public var tableFont: UIFont? + /// 工具条按钮项(nil → makeDefaultHeaderItems();外界通过 style 统一分发) + public var tableHeaderItems: [STMarkdownTableHeaderItem]? + /// 工具条按钮触控区宽度 + public var tableHeaderButtonWidth: CGFloat + /// 容器圆角蒙版,默认四角全圆 + public var tableCornerMask: CACornerMask public var imagePlaceholderTextColor: UIColor? public var imagePlaceholderBackgroundColor: UIColor? public var imagePlaceholderCaptionColor: UIColor? @@ -158,6 +170,13 @@ public struct STMarkdownStyle: @unchecked Sendable { tableHeaderTextColor: UIColor? = nil, tableBorderColor: UIColor? = nil, tableBackgroundColor: UIColor? = nil, + tableHeaderBarBackgroundColor: UIColor? = nil, + tableMinimumRowHeight: CGFloat = 35, + tableFont: UIFont? = nil, + tableHeaderItems: [STMarkdownTableHeaderItem]? = nil, + tableHeaderButtonWidth: CGFloat = 30, + tableCornerMask: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, + .layerMinXMaxYCorner, .layerMaxXMaxYCorner], imagePlaceholderTextColor: UIColor? = nil, imagePlaceholderBackgroundColor: UIColor? = nil, imagePlaceholderCaptionColor: UIColor? = nil, @@ -223,6 +242,12 @@ public struct STMarkdownStyle: @unchecked Sendable { self.tableHeaderTextColor = tableHeaderTextColor self.tableBorderColor = tableBorderColor self.tableBackgroundColor = tableBackgroundColor + self.tableHeaderBarBackgroundColor = tableHeaderBarBackgroundColor + self.tableMinimumRowHeight = tableMinimumRowHeight + self.tableFont = tableFont + self.tableHeaderItems = tableHeaderItems + self.tableHeaderButtonWidth = tableHeaderButtonWidth + self.tableCornerMask = tableCornerMask self.imagePlaceholderTextColor = imagePlaceholderTextColor self.imagePlaceholderBackgroundColor = imagePlaceholderBackgroundColor self.imagePlaceholderCaptionColor = imagePlaceholderCaptionColor diff --git a/Sources/STMarkdown/Table/STMarkdownTableCell.swift b/Sources/STMarkdown/Table/STMarkdownTableCell.swift index 2ba04a9..76a75ca 100644 --- a/Sources/STMarkdown/Table/STMarkdownTableCell.swift +++ b/Sources/STMarkdown/Table/STMarkdownTableCell.swift @@ -54,7 +54,20 @@ public final class STMarkdownTableCell: UICollectionViewCell { } func configure(with cellData: STMarkdownTableCellData, style: STMarkdownStyle) { - self.contentLabel.attributedText = cellData.attributedContent + let displayText: NSAttributedString + if let tableFont = style.tableFont { + let mutable = NSMutableAttributedString(attributedString: cellData.attributedContent) + let fullRange = NSRange(location: 0, length: mutable.length) + mutable.enumerateAttribute(.font, in: fullRange, options: []) { value, range, _ in + if let originalFont = value as? UIFont { + mutable.addAttribute(.font, value: originalFont.withSize(tableFont.pointSize), range: range) + } + } + displayText = mutable + } else { + displayText = cellData.attributedContent + } + self.contentLabel.attributedText = displayText let bgColor = style.tableBackgroundColor ?? UIColor.secondarySystemBackground self.contentView.backgroundColor = cellData.role.isHeader ? bgColor.withAlphaComponent(0.92) diff --git a/Sources/STMarkdown/Table/STMarkdownTableView.swift b/Sources/STMarkdown/Table/STMarkdownTableView.swift index f1b21b8..48ccb13 100644 --- a/Sources/STMarkdown/Table/STMarkdownTableView.swift +++ b/Sources/STMarkdown/Table/STMarkdownTableView.swift @@ -7,7 +7,60 @@ import UIKit -public final class STMarkdownTableView: UIView { +// MARK: - STMarkdownTableHeaderItem + +/// 顶部工具条的单个按钮描述。外界可组合内置工厂方法或自定义,赋给 `STMarkdownTableView.headerItems`。 +public struct STMarkdownTableHeaderItem { + public let identifier: String + public let image: UIImage? + /// handler 在主线程调用,参数为触发按钮的 tableView,便于访问数据或调用 renderFullTableImage()。 + public let handler: (STMarkdownTableView) -> Void + + public init(identifier: String, image: UIImage?, handler: @escaping (STMarkdownTableView) -> Void) { + self.identifier = identifier + self.image = image + self.handler = handler + } + + /// 复制按钮:将表格纯文本写入剪贴板,并触发 onCopyTable 回调。 + public static func copy() -> STMarkdownTableHeaderItem { + STMarkdownTableHeaderItem( + identifier: "copy", + image: UIImage(systemName: "doc.on.doc") + ) { tableView in + guard let tableData = tableView.tableData else { return } + UIPasteboard.general.string = tableData.plainText() + tableView.onCopyTable?() + tableView.showCopyFeedback() + } + } + + /// 下载按钮:触发 onDownloadTable 回调,由宿主实现具体保存逻辑。 + public static func download() -> STMarkdownTableHeaderItem { + STMarkdownTableHeaderItem( + identifier: "download", + image: UIImage(systemName: "square.and.arrow.down") + ) { tableView in + guard let tableData = tableView.tableData else { return } + tableView.onDownloadTable?(tableData) + } + } + + /// 全屏按钮:触发 onExpandTable 回调,与长按展开行为一致。 + public static func fullscreen() -> STMarkdownTableHeaderItem { + STMarkdownTableHeaderItem( + identifier: "fullscreen", + image: UIImage(systemName: "arrow.up.left.and.arrow.down.right") + ) { tableView in + guard let tableData = tableView.tableData else { return } + tableView.onExpandTable?(tableData) + } + } +} + +// MARK: - STMarkdownTableView + +open class STMarkdownTableView: UIView { /// 表格数据 façade。流式逐行追加(同列数、已有行内容不变、行数严格增多)时, /// 用 `performBatchUpdates` 插入新 section 并逐行淡入;否则全量 `reloadData()`。 @@ -31,7 +84,11 @@ public final class STMarkdownTableView: UIView { private var isApplyingAppend = false public var style: STMarkdownStyle = .default { - didSet { self.applyStyle(); self.reloadData() } + didSet { + self.applyStyle() + self.applyStyleHeaderItems() + self.reloadData() + } } public var onCitationTap: ((String) -> Void)? @@ -39,13 +96,24 @@ public final class STMarkdownTableView: UIView { public var onCopyTable: (() -> Void)? public var onDownloadTable: ((STMarkdownTableViewModel) -> Void)? + /// 顶部工具条按钮列表。默认为 [copy, download, fullscreen],赋新值后立即重建按钮栈。 + /// 赋空数组可隐藏所有按钮但保留标题行;配合 showsHeader=false 可完全隐藏工具条。 + public var headerItems: [STMarkdownTableHeaderItem] = [] { + didSet { self.rebuildButtonStack() } + } + + /// 顶部工具条左侧标题,默认 "表格"。 + public var headerTitle: String = "表格" { + didSet { self.titleLabel.text = self.headerTitle } + } + /// 顶部工具条高度(圆角卡片化后预留给「表格 / 复制 / 下载 / 全屏」)。 - public static let headerHeight: CGFloat = 44 + public static let headerHeight: CGFloat = 41 /// 整块表格圆角半径。 public var cornerRadius: CGFloat = 10 { didSet { self.layer.cornerRadius = self.cornerRadius } } - /// 是否展示顶部工具条。全屏详情页关闭(自带关闭按钮,避免重复表头与“全屏中再全屏”)。 + /// 是否展示顶部工具条。全屏详情页关闭(自带关闭按钮,避免重复表头与"全屏中再全屏")。 public var showsHeader: Bool = true { didSet { guard oldValue != self.showsHeader else { return } @@ -55,18 +123,18 @@ public final class STMarkdownTableView: UIView { } } + /// 单元格内边距,影响列宽/行高计算,与 computeSize 保持一致。 + public var cellInsets: UIEdgeInsets = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 10) + private let gridLayout: STMarkdownTableGridLayout private let collectionView: UICollectionView - private let cellInsets = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 10) private let headerBar = UIView() private let titleLabel = UILabel() private let buttonStack = UIStackView() - private let copyButton = UIButton(type: .system) - private let downloadButton = UIButton(type: .system) - private let fullscreenButton = UIButton(type: .system) private let headerSeparator = UIView() private var copyResetWorkItem: DispatchWorkItem? + private weak var copyButtonRef: UIButton? private weak var expandGesture: UILongPressGestureRecognizer? /// 全屏详情态:开启上下/左右滚动条与回弹,并关闭内置「长按展开」手势(由详情页接管长按菜单)。 @@ -88,14 +156,14 @@ public final class STMarkdownTableView: UIView { super.init(frame: .zero) self.clipsToBounds = true self.layer.cornerRadius = self.cornerRadius - self.layer.borderWidth = 0.5 + self.layer.borderWidth = 1 self.setupCollectionView() self.setupHeader() + self.headerItems = style.tableHeaderItems ?? self.makeDefaultHeaderItems() self.applyStyle() } - @available(*, unavailable) - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -122,28 +190,12 @@ public final class STMarkdownTableView: UIView { } private func setupHeader() { - self.titleLabel.text = "表格" + self.titleLabel.text = self.headerTitle self.titleLabel.font = UIFont.st_systemFont(ofSize: 14, weight: .medium) - self.copyButton.setImage(UIImage(systemName: "doc.on.doc"), for: .normal) - self.copyButton.addTarget(self, action: #selector(self.handleCopy), for: .touchUpInside) - self.downloadButton.setImage(UIImage(systemName: "square.and.arrow.down"), for: .normal) - self.downloadButton.addTarget(self, action: #selector(self.handleDownload), for: .touchUpInside) - self.fullscreenButton.setImage(UIImage(systemName: "arrow.up.left.and.arrow.down.right"), for: .normal) - self.fullscreenButton.addTarget(self, action: #selector(self.handleFullscreen), for: .touchUpInside) - - let imageConfig = UIImage.SymbolConfiguration(pointSize: 15, weight: .regular) - for button in [self.copyButton, self.downloadButton, self.fullscreenButton] { - button.setPreferredSymbolConfiguration(imageConfig, forImageIn: .normal) - button.widthAnchor.constraint(equalToConstant: 30).isActive = true - } - self.buttonStack.axis = .horizontal self.buttonStack.alignment = .center self.buttonStack.spacing = 6 - self.buttonStack.addArrangedSubview(self.copyButton) - self.buttonStack.addArrangedSubview(self.downloadButton) - self.buttonStack.addArrangedSubview(self.fullscreenButton) self.titleLabel.translatesAutoresizingMaskIntoConstraints = false self.buttonStack.translatesAutoresizingMaskIntoConstraints = false @@ -168,24 +220,61 @@ public final class STMarkdownTableView: UIView { ]) } + /// 从 headerItems 重建 buttonStack 中的所有按钮,每次 headerItems 变更时调用。 + private func rebuildButtonStack() { + self.buttonStack.arrangedSubviews.forEach { $0.removeFromSuperview() } + let symbolConfig = UIImage.SymbolConfiguration(pointSize: 15, weight: .regular) + let buttonWidth = self.style.tableHeaderButtonWidth + let secondaryColor = (self.style.tableHeaderTextColor ?? self.style.textColor).withAlphaComponent(0.6) + + for item in self.headerItems { + let button = UIButton(type: .system) + let isSystemSymbol = item.image?.isSymbolImage ?? false + if isSystemSymbol { + button.setPreferredSymbolConfiguration(symbolConfig, forImageIn: .normal) + } + button.setImage(item.image, for: .normal) + button.tintColor = secondaryColor + button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true + button.accessibilityIdentifier = item.identifier + button.addAction(UIAction { [weak self] _ in + guard let self else { return } + item.handler(self) + }, for: .touchUpInside) + if item.identifier == "copy" { + self.copyButtonRef = button + } + self.buttonStack.addArrangedSubview(button) + } + } + private func applyStyle() { let borderColor = self.style.tableBorderColor ?? UIColor.separator self.collectionView.backgroundColor = borderColor self.backgroundColor = borderColor self.gridLayout.interItemSpacing = 0.5 self.gridLayout.lineSpacing = 0.5 + self.gridLayout.minimumRowHeight = self.style.tableMinimumRowHeight self.layer.borderColor = borderColor.cgColor + self.layer.maskedCorners = self.style.tableCornerMask - let headerBg = self.style.tableBackgroundColor ?? UIColor.secondarySystemBackground + let headerBg = self.style.tableHeaderBarBackgroundColor + ?? self.style.tableBackgroundColor + ?? UIColor.secondarySystemBackground let secondaryColor = (self.style.tableHeaderTextColor ?? self.style.textColor).withAlphaComponent(0.6) self.headerBar.backgroundColor = headerBg self.headerSeparator.backgroundColor = borderColor self.titleLabel.textColor = secondaryColor - for button in [self.copyButton, self.downloadButton, self.fullscreenButton] { - button.tintColor = secondaryColor + self.buttonStack.arrangedSubviews.compactMap { $0 as? UIButton }.forEach { + $0.tintColor = secondaryColor } } + /// style 变更时同步按钮项(优先用 style.tableHeaderItems,否则 makeDefaultHeaderItems)。 + private func applyStyleHeaderItems() { + self.headerItems = self.style.tableHeaderItems ?? self.makeDefaultHeaderItems() + } + public override func layoutSubviews() { super.layoutSubviews() let headerHeight = self.showsHeader ? Self.headerHeight : 0 @@ -243,7 +332,7 @@ public final class STMarkdownTableView: UIView { metrics: STMarkdownTableGridLayout.ComputeSizeMetrics( fillWidth: true, containerWidth: containerWidth, - minimumRowHeight: 35, + minimumRowHeight: style.tableMinimumRowHeight, minimumColumnWidth: 56, maximumColumnWidth: 360, interItemSpacing: 0.5, @@ -419,28 +508,29 @@ public final class STMarkdownTableView: UIView { @objc private func handleExpandGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { guard gestureRecognizer.state == .began else { return } - self.expandTableIfPossible() - } - - private func expandTableIfPossible() { guard let tableData else { return } self.onExpandTable?(tableData) } - @objc private func handleCopy() { - guard let tableData else { return } - UIPasteboard.general.string = tableData.plainText() - self.onCopyTable?() - self.showCopyFeedback() - } + // MARK: - Open Overridable - @objc private func handleDownload() { - guard let tableData else { return } - self.onDownloadTable?(tableData) + /// 返回顶部工具条的默认按钮列表 [复制, 下载, 全屏]。子类可 override 替换默认集合。 + /// 外界也可在初始化后直接赋 headerItems 覆盖,无需子类化。 + open func makeDefaultHeaderItems() -> [STMarkdownTableHeaderItem] { + [.copy(), .download(), .fullscreen()] } - @objc private func handleFullscreen() { - self.expandTableIfPossible() + /// 复制成功后的视觉反馈。默认将图标切换为对勾,~1.2s 后还原。 + /// 子类可 override 接入宿主 Toast/HUD 系统。 + open func showCopyFeedback() { + self.copyResetWorkItem?.cancel() + let originalImage = self.copyButtonRef?.image(for: .normal) + self.copyButtonRef?.setImage(UIImage(systemName: "checkmark"), for: .normal) + let workItem = DispatchWorkItem { [weak self] in + self?.copyButtonRef?.setImage(originalImage, for: .normal) + } + self.copyResetWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 1.2, execute: workItem) } /// 将整张表格(含离屏行列)渲染为图片,供「复制为图片 / 保存到相册」使用。 @@ -470,17 +560,6 @@ public final class STMarkdownTableView: UIView { self.collectionView.layoutIfNeeded() return image } - - /// 复制成功后将图标临时切换为对勾,~1.2s 后还原,提供轻量内建反馈(无需宿主接线)。 - private func showCopyFeedback() { - self.copyResetWorkItem?.cancel() - self.copyButton.setImage(UIImage(systemName: "checkmark"), for: .normal) - let workItem = DispatchWorkItem { [weak self] in - self?.copyButton.setImage(UIImage(systemName: "doc.on.doc"), for: .normal) - } - self.copyResetWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + 1.2, execute: workItem) - } } extension STMarkdownTableView: UICollectionViewDelegate { From e1889496c6bb3af1712e894e977525848a2a005c Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 1 Jul 2026 16:25:31 +0800 Subject: [PATCH 2/2] Add tests for custom header items and button management in STMarkdownTableView - Implemented testCustomHeaderItemsAreVisibleAfterLayout to verify visibility and layout of custom header buttons. - Added testReassigningHeaderItemsRemovesOldButtons to ensure old buttons are removed when header items are reassigned. - Enhanced STMarkdownTableView to rebuild button stack correctly when header items change, ensuring proper button management and layout. --- .../STMarkdownFixTests.swift | 59 +++++++++++++++++++ .../Table/STMarkdownTableView.swift | 11 +++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Example/STBaseProjectExampleTests/STMarkdownFixTests.swift b/Example/STBaseProjectExampleTests/STMarkdownFixTests.swift index 7746565..74175a2 100644 --- a/Example/STBaseProjectExampleTests/STMarkdownFixTests.swift +++ b/Example/STBaseProjectExampleTests/STMarkdownFixTests.swift @@ -259,6 +259,47 @@ final class STMarkdownTableViewModelCitationTests: XCTestCase { @MainActor final class STMarkdownTableViewCitationTapTests: XCTestCase { + func testCustomHeaderItemsAreVisibleAfterLayout() { + let image = self.makeTestIcon() + let style = STMarkdownStyle( + font: .systemFont(ofSize: 14), + textColor: .label, + lineHeight: 18, + kern: 0, + tableHeaderItems: [ + STMarkdownTableHeaderItem(identifier: "custom-copy", image: image) { _ in }, + STMarkdownTableHeaderItem(identifier: "custom-fullscreen", image: image) { _ in } + ], + tableHeaderButtonWidth: 16 + ) + let tableView = STMarkdownTableView(style: style) + + tableView.frame = CGRect(x: 0, y: 0, width: 320, height: 120) + tableView.layoutIfNeeded() + + let buttons = self.headerButtons(in: tableView) + XCTAssertEqual(buttons.map(\.accessibilityIdentifier), ["custom-copy", "custom-fullscreen"]) + XCTAssertTrue(buttons.allSatisfy { !$0.frame.isEmpty }, "自定义 headerItems 按钮应有稳定非零布局尺寸") + XCTAssertTrue(buttons.allSatisfy { $0.image(for: .normal) != nil }, "自定义 headerItems 按钮应保留配置的图片") + } + + func testReassigningHeaderItemsRemovesOldButtons() { + let image = self.makeTestIcon() + let tableView = STMarkdownTableView(style: .default) + tableView.headerItems = [ + STMarkdownTableHeaderItem(identifier: "first", image: image) { _ in }, + STMarkdownTableHeaderItem(identifier: "second", image: image) { _ in } + ] + tableView.headerItems = [ + STMarkdownTableHeaderItem(identifier: "third", image: image) { _ in } + ] + + tableView.frame = CGRect(x: 0, y: 0, width: 320, height: 120) + tableView.layoutIfNeeded() + + XCTAssertEqual(self.headerButtons(in: tableView).map(\.accessibilityIdentifier), ["third"]) + } + func testCitationTapCallbackFiredWhenCellSelected() { let style = STMarkdownStyle.default let tableView = STMarkdownTableView(style: style) @@ -481,4 +522,22 @@ final class STMarkdownTableViewCitationTapTests: XCTestCase { private func gradientLayer(named name: String, in view: UIView) -> CAGradientLayer? { view.layer.sublayers?.first(where: { $0.name == name }) as? CAGradientLayer } + + private func headerButtons(in view: UIView) -> [UIButton] { + view.subviews.flatMap { subview -> [UIButton] in + var buttons = self.headerButtons(in: subview) + if let button = subview as? UIButton { + buttons.insert(button, at: 0) + } + return buttons + } + } + + private func makeTestIcon() -> UIImage { + let renderer = UIGraphicsImageRenderer(size: CGSize(width: 16, height: 16)) + return renderer.image { context in + UIColor.black.setFill() + context.fill(CGRect(x: 3, y: 3, width: 10, height: 10)) + } + } } diff --git a/Sources/STMarkdown/Table/STMarkdownTableView.swift b/Sources/STMarkdown/Table/STMarkdownTableView.swift index 48ccb13..520e883 100644 --- a/Sources/STMarkdown/Table/STMarkdownTableView.swift +++ b/Sources/STMarkdown/Table/STMarkdownTableView.swift @@ -160,6 +160,7 @@ open class STMarkdownTableView: UIView { self.setupCollectionView() self.setupHeader() self.headerItems = style.tableHeaderItems ?? self.makeDefaultHeaderItems() + self.rebuildButtonStack() self.applyStyle() } @@ -211,7 +212,7 @@ open class STMarkdownTableView: UIView { self.buttonStack.trailingAnchor.constraint(equalTo: self.headerBar.trailingAnchor, constant: -10), self.buttonStack.centerYAnchor.constraint(equalTo: self.headerBar.centerYAnchor), - self.buttonStack.heightAnchor.constraint(equalTo: self.headerBar.heightAnchor), + self.buttonStack.heightAnchor.constraint(equalToConstant: Self.headerHeight), self.headerSeparator.leadingAnchor.constraint(equalTo: self.headerBar.leadingAnchor), self.headerSeparator.trailingAnchor.constraint(equalTo: self.headerBar.trailingAnchor), @@ -222,11 +223,13 @@ open class STMarkdownTableView: UIView { /// 从 headerItems 重建 buttonStack 中的所有按钮,每次 headerItems 变更时调用。 private func rebuildButtonStack() { - self.buttonStack.arrangedSubviews.forEach { $0.removeFromSuperview() } + self.buttonStack.arrangedSubviews.forEach { + self.buttonStack.removeArrangedSubview($0) + $0.removeFromSuperview() + } let symbolConfig = UIImage.SymbolConfiguration(pointSize: 15, weight: .regular) let buttonWidth = self.style.tableHeaderButtonWidth let secondaryColor = (self.style.tableHeaderTextColor ?? self.style.textColor).withAlphaComponent(0.6) - for item in self.headerItems { let button = UIButton(type: .system) let isSystemSymbol = item.image?.isSymbolImage ?? false @@ -235,7 +238,9 @@ open class STMarkdownTableView: UIView { } button.setImage(item.image, for: .normal) button.tintColor = secondaryColor + button.imageView?.contentMode = .scaleAspectFit button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true + button.heightAnchor.constraint(equalToConstant: buttonWidth).isActive = true button.accessibilityIdentifier = item.identifier button.addAction(UIAction { [weak self] _ in guard let self else { return }