You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS开发中如何提取并修改字符串前两个单词(适配俄语变格)

问题描述

我正在开发一款适配俄语变格语法的iOS应用,场景如下:

  • 应用包含FirstVC和SecondVC两个视图控制器,各自带有UITableView;
  • 用户点击FirstVC的单元格时,单元格文本会传递到SecondVC的表格表头;
  • SecondVC的表头文本需符合俄语变格规则,目前已实现提取并修改字符串首个单词的逻辑,现在需要扩展功能,支持修改字符串前两个单词的情况,请求基于现有逻辑的实现方法。

现有SecondVC代码

import UIKit

class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!

var names: [Cell2Model] = []

var textForChanging: String = ""

var switchedFirstWord: String {
    switch textForChanging.firstWord {
    case "Соревнование": return "соревнования" //translates as "competition"
    default: return "none" } }

var restoOfTheName: String {
    var string = textForChanging
    string.enumerateSubstrings(in: string.startIndex..., options: .byWords) { (_, _, enclosingRange, stop) in
        string.removeSubrange(enclosingRange)
        stop = true }
    return string }

var adjustedEventNameText: String {
    return switchedFirstWord + " " + restoOfTheName }

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.delegate = self
    tableView.dataSource = self
    
    // names will be taken from UITextFields on the fourth view controller
    names = [
        (Cell2Model(name: "Participant 1")),
        (Cell2Model(name: "Participant 2")),
        (Cell2Model(name: "Participant 3")) ]
    
    let tableHeader = UIView()
    tableView.tableHeaderView = tableHeader
    
    let labelOne = UILabel(frame: tableHeader.bounds)
    labelOne.text = "СПИСОК"
    labelOne.font = UIFont(name:"Arial-BoldMT", size: 17.0)
    labelOne.numberOfLines = 0
    labelOne.lineBreakMode = .byWordWrapping
    labelOne.textAlignment = .center
    labelOne.translatesAutoresizingMaskIntoConstraints = false
    
    let labelTwo = UILabel(frame: tableHeader.bounds)
    labelTwo.translatesAutoresizingMaskIntoConstraints = false
    labelTwo.text = "участников \(adjustedEventNameText)"
    labelTwo.font = UIFont(name:"Arial-BoldMT", size: 17.0)
    labelTwo.numberOfLines = 0
    labelTwo.lineBreakMode = .byWordWrapping
    labelTwo.textAlignment = .center
    labelTwo.translatesAutoresizingMaskIntoConstraints = false
    
    tableHeader.addSubview(labelOne)
    tableHeader.addSubview(labelTwo)

    let g = tableHeader.layoutMarginsGuide
    
    NSLayoutConstraint.activate([
        labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
        labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
        labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
        labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
        labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
        labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0), ])
    
    let c = labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0)
    c.priority = .required - 1
    c.isActive = true
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if let tableHeader = tableView.tableHeaderView {
        let fitSize: CGSize = CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude)
        let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
        let height: CGFloat = sz.height
        var headerFrame = tableHeader.frame
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            tableHeader.frame = headerFrame
            tableView.tableHeaderView = tableHeader } } }

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return names.count }

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "secondCell", for: indexPath) as! SecondCell
    let Name = names[indexPath.row]
    cell.label.text = Name.name
    return cell } }

extension StringProtocol {
var byWords: [SubSequence] { components(separated: .byWords) }

func components(separated options: String.EnumerationOptions)-> [SubSequence] {
    var components: [SubSequence] = []
    enumerateSubstrings(in: startIndex..., options: options) { _, range, _, _ in components.append(self[range]) }
    return components }

var firstWord: SubSequence? {
    var word: SubSequence?
    enumerateSubstrings(in: startIndex..., options: .byWords) { _, range, _, stop in
        word = self[range]
        stop = true }
    return word } }

现有FirstVC代码

import UIKit

class FirstVC: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!

var names: [Cell1Model] = []

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.delegate = self
    tableView.dataSource = self
    
    // names will be taken from UITextFields on the third view controller
    names = [
        Cell1Model(name: "Соревнование по легкой атлетике"), // translates as "athletics competition (or "competition on athletics" to put it into the Russian words order)
        Cell1Model(name: "Благотворительное соревнование по легкой атлетике") // translates as "athletics charity competition" (or "charity competition on athletics" in the Russian words order)
    ]
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    if let vc = storyboard?.instantiateViewController(withIdentifier: "SecondVC") as? SecondVC {
        vc.textForChanging = names[indexPath.row].name!
        self.navigationController?.pushViewController(vc, animated: true) } }

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return names.count }

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell
    let Name = names[indexPath.row]
    cell.label.text = Name.name
    return cell }

解决方案

基于现有代码逻辑,通过以下步骤扩展支持前两个单词的变格处理:

1. 扩展StringProtocol,新增多单词操作方法

在现有StringProtocol扩展中添加获取前N个单词、移除前N个单词的方法,兼容原有逻辑:

extension StringProtocol {
    var byWords: [SubSequence] { components(separated: .byWords) }
    
    func components(separated options: String.EnumerationOptions)-> [SubSequence] {
        var components: [SubSequence] = []
        enumerateSubstrings(in: startIndex..., options: options) { _, range, _, _ in components.append(self[range]) }
        return components
    }
    
    var firstWord: SubSequence? {
        var word: SubSequence?
        enumerateSubstrings(in: startIndex..., options: .byWords) { _, range, _, stop in
            word = self[range]
            stop = true
        }
        return word
    }
    
    // 获取前n个单词
    func firstWords(_ count: Int) -> [SubSequence] {
        var words: [SubSequence] = []
        enumerateSubstrings(in: startIndex..., options: .byWords) { _, range, _, stop in
            words.append(self[range])
            if words.count == count {
                stop = true
            }
        }
        return words
    }
    
    // 移除前n个单词后的剩余字符串
    func removingFirstWords(_ count: Int) -> String {
        var string = String(self)
        var removedCount = 0
        string.enumerateSubstrings(in: string.startIndex..., options: .byWords) { _, enclosingRange, _, stop in
            string.removeSubrange(enclosingRange)
            removedCount += 1
            if removedCount == count {
                stop = true
            }
        }
        return string.trimmingCharacters(in: .whitespaces)
    }
}

2. 修改SecondVC,添加双单词变格逻辑

拆分变格逻辑到独立方法,根据输入文本判断需要变格1个还是2个单词:

class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // ... 原有代码保持不变 ...
    
    // 处理单个单词的变格
    private func inflectSingleWord(_ word: SubSequence) -> String {
        switch word {
        case "Соревнование": return "соревнования"
        default: return String(word)
        }
    }
    
    // 处理两个单词的变格(示例:"Благотворительное соревнование")
    private func inflectTwoWords(_ words: [SubSequence]) -> String {
        guard words.count >= 2 else { return inflectSingleWord(words.first!) }
        let firstWord = words[0]
        let secondWord = words[1]
        // 根据俄语变格规则添加对应处理
        switch (firstWord, secondWord) {
        case ("Благотворительное", "Соревнование"):
            return "благотворительного соревнования"
        // 可添加更多双单词变格规则
        default:
            return "\(inflectSingleWord(firstWord)) \(inflectSingleWord(secondWord))"
        }
    }
    
    var adjustedEventNameText: String {
        let allWords = textForChanging.byWords
        switch allWords.count {
        case 1:
            return inflectSingleWord(allWords[0])
        case >=2:
            let firstTwo = allWords.prefix(2)
            // 判断是否需要双单词变格,可根据实际规则调整条件
            let needsDoubleInflection = firstTwo.contains("Благотворительное") && firstTwo.contains("Соревнование")
            if needsDoubleInflection {
                let inflectedTwo = inflectTwoWords(Array(firstTwo))
                let rest = textForChanging.removingFirstWords(2)
                return rest.isEmpty ? inflectedTwo : "\(inflectedTwo) \(rest)"
            } else {
                // 仅变格第一个单词,兼容原有逻辑
                let inflectedFirst = inflectSingleWord(allWords[0])
                let rest = textForChanging.removingFirstWords(1)
                return rest.isEmpty ? inflectedFirst : "\(inflectedFirst) \(rest)"
            }
        default:
            return textForChanging
        }
    }
    
    // ... 原有代码保持不变 ...
}

逻辑说明

  • 通过新增的firstWords(_:)和removingFirstWords(_:)方法,简化多单词的提取与剩余文本处理;
  • 拆分变格逻辑到独立方法,便于后续扩展更多俄语变格规则;
  • 在adjustedEventNameText中自动判断文本类型,优先处理双单词变格场景,同时保留原有单单词变格的兼容性。

内容的提问来源于stack exchange,提问作者CalebGates

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:34:57