6.5 C
London
Monday, February 12, 2024

ios – Producing a dynamic information desk grid for the OpenAI API response


I am at the moment creating a chatbot for an iOS app, leveraging the OpenAI API. One of many challenges I am going through is dynamically making a desk grid. My goal is to show every NSAttributedString inside a dynamically generated grid, mirroring the construction depicted right here:

enter image description here

what i get proper now from my chatbot response which is openai api is the road breaks like this “—|—|—|—|— |

” which isn’t skilled:

enter image description here

The codes for desk grid want to seem contained in the:

    mutating inside func visitTable(_ desk: Desk) -> NSAttributedString {
        let consequence = NSMutableAttributedString()

        return consequence
    }

These are my present codes, the place I am utilizing Markdownosaur to focus on the functioning parts. My query is centered across the strains that separate the rows and columns. I am in search of refinements to realize a professional-looking desk grid, akin to the one depicted within the first picture. How can I improve the presentation of the desk strains for a extra polished look?

//
//  MarkdownAttributedStringParser.swift
//  Besa
//
//  Created by Artit on 12/01/24.
//

import Basis
import UIKit
import Markdown
import Highlighter

/// Based mostly on the supply code from Christian Selig
/// https://github.com/christianselig/Markdownosaur/blob/major/Sources/Markdownosaur/Markdownosaur.swift

public struct MarkdownAttributedStringParser: MarkupVisitor {
    let baseFontSize: CGFloat = UIFont.preferredFont(forTextStyle: .physique).pointSize
    let highlighter: Highlighter = {
        let highlighter = Highlighter()!
        highlighter.setTheme("stackoverflow-dark")
        return highlighter
    }()
    
    let newLineFontSize: CGFloat = 12

    public init() {}
    
    public mutating func attributedString(from doc: Doc) -> NSAttributedString {
        return go to(doc)
    }
    
    mutating func parserResults(from doc: Doc) -> [ParserResult] {
        var outcomes = [ParserResult]()
        var currentAttrString = NSMutableAttributedString()
        
        func appendCurrentAttrString(tableData: [[NSAttributedString]]) {
            if !currentAttrString.string.isEmpty {
                let currentAttrStringToAppend = (attempt? AttributedString(currentAttrString, together with: .uiKit)) ?? AttributedString(stringLiteral: currentAttrString.string)
                outcomes.append(.init(attributedString: currentAttrStringToAppend, isCodeBlock: false, codeBlockLanguage: nil))
            }
        }

        doc.youngsters.forEach { markup in
            let attrString = go to(markup)
            if let codeBlock = markup as? CodeBlock {
                appendCurrentAttrString(tableData: [])
                let attrStringToAppend = (attempt? AttributedString(attrString, together with: .uiKit)) ?? AttributedString(stringLiteral: attrString.string)
                outcomes.append(.init(attributedString: attrStringToAppend, isCodeBlock: true, codeBlockLanguage: codeBlock.language))
                currentAttrString = NSMutableAttributedString()
            } else {
                currentAttrString.append(attrString)
            }
        }

        appendCurrentAttrString(tableData: [])
        return outcomes
    }
    
    mutating public func defaultVisit(_ markup: Markup) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in markup.youngsters {
            consequence.append(go to(baby))
        }
        
        return consequence
    }
    
    mutating public func visitText(_ textual content: Textual content) -> NSAttributedString {
        return NSAttributedString(string: textual content.plainText, attributes: [.font: UIFont.systemFont(ofSize: baseFontSize, weight: .regular)])
    }
    
    mutating public func visitEmphasis(_ emphasis: Emphasis) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in emphasis.youngsters {
            consequence.append(go to(baby))
        }
        
        consequence.applyEmphasis()
        
        return consequence
    }
    
    
    mutating inside func visitTable(_ desk: Desk) -> NSAttributedString {
        let consequence = NSMutableAttributedString()

        return consequence
    }




    
    
    mutating public func visitStrong(_ sturdy: Sturdy) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in sturdy.youngsters {
            consequence.append(go to(baby))
        }
        
        consequence.applyStrong()
        
        return consequence
    }
    
    mutating public func visitParagraph(_ paragraph: Paragraph) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in paragraph.youngsters {
            consequence.append(go to(baby))
        }
        
        if paragraph.hasSuccessor {
            consequence.append(paragraph.isContainedInList ? .singleNewline(withFontSize: newLineFontSize) : .doubleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
    
    mutating public func visitHeading(_ heading: Heading) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in heading.youngsters {
            consequence.append(go to(baby))
        }
        
        consequence.applyHeading(withLevel: heading.degree)
        
        if heading.hasSuccessor {
            consequence.append(.doubleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
    
    mutating public func visitLink(_ hyperlink: Hyperlink) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in hyperlink.youngsters {
            consequence.append(go to(baby))
        }
        
        let url = hyperlink.vacation spot != nil ? URL(string: hyperlink.vacation spot!) : nil
        
        consequence.applyLink(withURL: url)
        
        return consequence
    }
    
    mutating public func visitInlineCode(_ inlineCode: InlineCode) -> NSAttributedString {
        return NSAttributedString(string: inlineCode.code, attributes: [.font: UIFont.monospacedSystemFont(ofSize: baseFontSize - 1.0, weight: .regular), .foregroundColor: UIColor.systemPink])
    }
    
    public func visitCodeBlock(_ codeBlock: CodeBlock) -> NSAttributedString {
        let consequence = NSMutableAttributedString(attributedString: highlighter.spotlight(codeBlock.code, as: codeBlock.language) ?? NSAttributedString(string: codeBlock.code))
        
        if codeBlock.hasSuccessor {
            consequence.append(.singleNewline(withFontSize: newLineFontSize))
        }
    
        return consequence
    }
    
    mutating public func visitStrikethrough(_ strikethrough: Strikethrough) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in strikethrough.youngsters {
            consequence.append(go to(baby))
        }
        
        consequence.applyStrikethrough()
        
        return consequence
    }
    
    mutating public func visitUnorderedList(_ unorderedList: UnorderedList) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        let font = UIFont.systemFont(ofSize: baseFontSize, weight: .common)
                
        for listItem in unorderedList.listItems {
            var listItemAttributes: [NSAttributedString.Key: Any] = [:]
            
            let listItemParagraphStyle = NSMutableParagraphStyle()
            
            let baseLeftMargin: CGFloat = 15.0
            let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(unorderedList.listDepth))
            let spacingFromIndex: CGFloat = 8.0
            let bulletWidth = ceil(NSAttributedString(string: "•", attributes: [.font: font]).dimension().width)
            let firstTabLocation = leftMarginOffset + bulletWidth
            let secondTabLocation = firstTabLocation + spacingFromIndex
            
            listItemParagraphStyle.tabStops = [
                NSTextTab(textAlignment: .right, location: firstTabLocation),
                NSTextTab(textAlignment: .left, location: secondTabLocation)
            ]
            
            listItemParagraphStyle.headIndent = secondTabLocation
            
            listItemAttributes[.paragraphStyle] = listItemParagraphStyle
            listItemAttributes[.font] = UIFont.systemFont(ofSize: baseFontSize, weight: .common)
            listItemAttributes[.listDepth] = unorderedList.listDepth
            
            let listItemAttributedString = go to(listItem).mutableCopy() as! NSMutableAttributedString
            listItemAttributedString.insert(NSAttributedString(string: "t•t", attributes: listItemAttributes), at: 0)
            
            consequence.append(listItemAttributedString)
        }
        
        if unorderedList.hasSuccessor {
            consequence.append(.doubleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
    
    mutating public func visitListItem(_ listItem: ListItem) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in listItem.youngsters {
            consequence.append(go to(baby))
        }
        
        if listItem.hasSuccessor {
            consequence.append(.singleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
    
    mutating public func visitOrderedList(_ orderedList: OrderedList) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for (index, listItem) in orderedList.listItems.enumerated() {
            var listItemAttributes: [NSAttributedString.Key: Any] = [:]
            
            let font = UIFont.systemFont(ofSize: baseFontSize, weight: .common)
            let numeralFont = UIFont.monospacedDigitSystemFont(ofSize: baseFontSize, weight: .common)
            
            let listItemParagraphStyle = NSMutableParagraphStyle()
            
            // Implement a base quantity to be spaced from the left facet always to higher visually differentiate it as a listing
            let baseLeftMargin: CGFloat = 15.0
            let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(orderedList.listDepth))
            
            // Seize the very best quantity to be displayed and measure its width (sure usually some digits are wider than others however since we're utilizing the numeral mono font all would be the similar width on this case)
            let highestNumberInList = orderedList.childCount
            let numeralColumnWidth = ceil(NSAttributedString(string: "(highestNumberInList).", attributes: [.font: numeralFont]).dimension().width)
            
            let spacingFromIndex: CGFloat = 8.0
            let firstTabLocation = leftMarginOffset + numeralColumnWidth
            let secondTabLocation = firstTabLocation + spacingFromIndex
            
            listItemParagraphStyle.tabStops = [
                NSTextTab(textAlignment: .right, location: firstTabLocation),
                NSTextTab(textAlignment: .left, location: secondTabLocation)
            ]
            
            listItemParagraphStyle.headIndent = secondTabLocation
            
            listItemAttributes[.paragraphStyle] = listItemParagraphStyle
            listItemAttributes[.font] = font
            listItemAttributes[.listDepth] = orderedList.listDepth

            let listItemAttributedString = go to(listItem).mutableCopy() as! NSMutableAttributedString
            
            // Identical as the traditional record attributes, however for prettiness in formatting we need to use the cool monospaced numeral font
            var numberAttributes = listItemAttributes
            numberAttributes[.font] = numeralFont
            
            let numberAttributedString = NSAttributedString(string: "t(index + 1).t", attributes: numberAttributes)
            listItemAttributedString.insert(numberAttributedString, at: 0)
            
            consequence.append(listItemAttributedString)
        }
        
        if orderedList.hasSuccessor {
            consequence.append(orderedList.isContainedInList ? .singleNewline(withFontSize: newLineFontSize) : .doubleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
    
    mutating public func visitBlockQuote(_ blockQuote: BlockQuote) -> NSAttributedString {
        let consequence = NSMutableAttributedString()
        
        for baby in blockQuote.youngsters {
            var quoteAttributes: [NSAttributedString.Key: Any] = [:]
            
            let quoteParagraphStyle = NSMutableParagraphStyle()
            
            let baseLeftMargin: CGFloat = 15.0
            let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(blockQuote.quoteDepth))
            
            quoteParagraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: leftMarginOffset)]
            
            quoteParagraphStyle.headIndent = leftMarginOffset
            
            quoteAttributes[.paragraphStyle] = quoteParagraphStyle
            quoteAttributes[.font] = UIFont.systemFont(ofSize: baseFontSize, weight: .common)
            quoteAttributes[.listDepth] = blockQuote.quoteDepth
            
            let quoteAttributedString = go to(baby).mutableCopy() as! NSMutableAttributedString
            quoteAttributedString.insert(NSAttributedString(string: "t", attributes: quoteAttributes), at: 0)
            
            quoteAttributedString.addAttribute(.foregroundColor, worth: UIColor.systemGray)
            
            consequence.append(quoteAttributedString)
        }
        
        if blockQuote.hasSuccessor {
            consequence.append(.doubleNewline(withFontSize: newLineFontSize))
        }
        
        return consequence
    }
}

// MARK: - Extensions Land




extension NSMutableAttributedString {
    func applyEmphasis() {
        enumerateAttribute(.font, in: NSRange(location: 0, size: size), choices: []) { worth, vary, cease in
            guard let font = worth as? UIFont else { return }
            
            let newFont = font.apply(newTraits: .traitItalic)
            addAttribute(.font, worth: newFont, vary: vary)
        }
    }
    
    func applyStrong() {
        enumerateAttribute(.font, in: NSRange(location: 0, size: size), choices: []) { worth, vary, cease in
            guard let font = worth as? UIFont else { return }
            
            let newFont = font.apply(newTraits: .traitBold)
            addAttribute(.font, worth: newFont, vary: vary)
        }
    }
    
    func applyLink(withURL url: URL?) {
        addAttribute(.foregroundColor, worth: UIColor.systemBlue)
        
        if let url = url {
            addAttribute(.hyperlink, worth: url)
        }
    }
    
    func applyBlockquote() {
        addAttribute(.foregroundColor, worth: UIColor.systemGray)
    }
    
    func applyHeading(withLevel headingLevel: Int) {
        enumerateAttribute(.font, in: NSRange(location: 0, size: size), choices: []) { worth, vary, cease in
            guard let font = worth as? UIFont else { return }
            
            let newFont = font.apply(newTraits: .traitBold, newPointSize: 28.0 - CGFloat(headingLevel * 2))
            addAttribute(.font, worth: newFont, vary: vary)
        }
    }
    
    func applyStrikethrough() {
        addAttribute(.strikethroughStyle, worth: NSUnderlineStyle.single.rawValue)
    }
    
    
    
    
}




extension UIFont {
    func apply(newTraits: UIFontDescriptor.SymbolicTraits, newPointSize: CGFloat? = nil) -> UIFont {
        var existingTraits = fontDescriptor.symbolicTraits
        existingTraits.insert(newTraits)
        
        guard let newFontDescriptor = fontDescriptor.withSymbolicTraits(existingTraits) else { return self }
        return UIFont(descriptor: newFontDescriptor, dimension: newPointSize ?? pointSize)
    }
}

extension ListItemContainer {
    /// Depth of the record if nested inside others. Index begins at 0.
    var listDepth: Int {
        var index = 0

        var currentElement = mother or father

        whereas currentElement != nil {
            if currentElement is ListItemContainer {
                index += 1
            }

            currentElement = currentElement?.mother or father
        }
        
        return index
    }
}

extension BlockQuote {
    /// Depth of the quote if nested inside others. Index begins at 0.
    var quoteDepth: Int {
        var index = 0

        var currentElement = mother or father

        whereas currentElement != nil {
            if currentElement is BlockQuote {
                index += 1
            }

            currentElement = currentElement?.mother or father
        }
        
        return index
    }
}

extension NSAttributedString.Key {
    static let listDepth = NSAttributedString.Key("ListDepth")
    static let quoteDepth = NSAttributedString.Key("QuoteDepth")
}

extension NSMutableAttributedString {
    func addAttribute(_ title: NSAttributedString.Key, worth: Any) {
        addAttribute(title, worth: worth, vary: NSRange(location: 0, size: size))
    }
    
    func addAttributes(_ attrs: [NSAttributedString.Key : Any]) {
        addAttributes(attrs, vary: NSRange(location: 0, size: size))
    }
}

extension Markup {
    /// Returns true if this ingredient has sibling components after it.
    var hasSuccessor: Bool {
        guard let childCount = mother or father?.childCount else { return false }
        return indexInParent < childCount - 1
    }
    
    var isContainedInList: Bool {
        var currentElement = mother or father

        whereas currentElement != nil {
            if currentElement is ListItemContainer {
                return true
            }

            currentElement = currentElement?.mother or father
        }
        
        return false
    }
}




extension NSAttributedString {
    static func singleNewline(withFontSize fontSize: CGFloat) -> NSAttributedString {
        return NSAttributedString(string: "n", attributes: [.font: UIFont.systemFont(ofSize: fontSize)])
    }

    static func doubleNewline(withFontSize fontSize: CGFloat) -> NSAttributedString {
        return NSAttributedString(string: "nn", attributes: [.font: UIFont.systemFont(ofSize: fontSize, weight: .regular)])
    }
}

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here