如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

huangapple go评论115阅读模式
英文:

How To Expand UITableViewCell's UILabel's Background Property When Text Is Being Generated?

问题

I cannot get my label's background property to properly expand/grow with the text as it is being inputted into the label.

我无法让标签的背景属性随着输入到标签中的文本正确地扩展/增长。

I've made a UITableView with a custom cell. This custom cell's file is a .xib file (called StoryCell.xib). The .xib file is associated with a .swift file that I can enter code in. In this .swift file (it is called StoryCell.swift), I have a typing animation function where whatever string goes into this label, it will be typed out character by character until the full text of that string variable is fully typed out over a period of time. With this label, I have a background property set. (for effects).

我创建了一个带有自定义单元格的UITableView。这个自定义单元格的文件是一个.xib文件(称为StoryCell.xib)。.xib文件关联着一个可以输入代码的.swift文件。在这个.swift文件中(它被称为StoryCell.swift),我有一个打字动画函数,无论什么字符串进入这个标签,它都会逐字符地打印出来,直到字符串变量的完整文本在一段时间内完全打印出来。对于这个标签,我设置了一个背景属性(用于效果)。

My problem is that when the characters and words are being typed into the label with the inputted string variable, the background property does not properly expand with each line of the text being typed out. Playing around with different code and enabling different properties and constraints, my label's background will either be pre-determined (meaning that the background property will automatically be set to certain dimensions [a certain box] to fit the entirety of the text once it's fully typed out) or that my background property just stays as one single line, cutting off text after that line moves onto another line to write more text.

我的问题是,当字符和单词被输入到标签中时,使用输入的字符串变量,背景属性不会随着文本的每一行被打印出而正确扩展。尝试不同的代码并启用不同的属性和约束后,我的标签的背景要么是预定的(意思是一旦完全打印出文本,背景属性将自动设置为某些尺寸[某个框]以适应整个文本),要么我的背景属性仅保持为一条单独的线,当该行移动到另一行以写更多文本时,会截断文本。

I cannot for the life of me figure out what to do. If anyone has any idea on what I can do or resources I can look into, I'll be eternally grateful.

我无法弄清楚该怎么做。如果有人有任何想法或可以查阅的资源,我将永远感激不尽。

TL;DR I want my UILabel's background property in my custom cell (in a UITableView) to expand line by line with my text being typed out by my typing function, but I can't achieve this effect. How to get?

总结:我想要我的自定义单元格中(在UITableView中)的UILabel的背景属性随着我的打字函数逐行扩展,但我无法实现这种效果。怎么做?

My StoryCell.swift's code for reference if you need:

如果需要,这是我的StoryCell.swift的代码供参考:

import UIKit

class StoryCell: UITableViewCell {

var typingTimer: Timer? // Add typingTimer property

@IBOutlet weak var label: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state
}

// Add startTypingAnimation method
func startTypingAnimation(withText text: String) {
    label.text = "" // Clear existing text
    var charIndex = 0

    // Create a timer to simulate typing animation
    typingTimer?.invalidate() // this code line here is to make sure that no previous timers are running
    
    typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
        if charIndex < text.count {
            let index = text.index(text.startIndex, offsetBy: charIndex) // The text.index(_, offsetBy:) method is used to obtain the index of a character in the string text. It takes two arguments: the starting index and an offset. The offset specifies the number of positions to move from the starting index.

            let nextChar = String(text[index]) // the character at the position specified by the offset value
            
            self.label.text?.append(nextChar)
            charIndex += 1
        } else {
            timer.invalidate()
        }
    }
}

}

英文:

I cannot get my label's background property to properly expand/grow with the text as it is being inputted into the label.

I've made a UITableView with a custom cell. This custom cell's file is a .xib file (called StoryCell.xib). The .xib file is associated with a .swift file that I can enter code in. In this .swift file (it is called StoryCell.swift), I have a typing animation function where whatever string goes into this label, it will be typed out character by character until the full text of that string variable is fully typed out over a period of time. With this label, I have a background property set. (for effects).

My problem is that when the characters and words are being typed into the label with the inputted string variable, the background property does not properly expand with each line of the text being typed out. Playing around with different code and enabling different properties and constraints, my label's background will either be pre-determined (meaning that the background property will automatically be set to certain dimensions [a certain box] to fit the entirety of the text once it's fully typed out) or that my background property just stays as one single line, cutting off text after that line moves onto another line to write more text.

I cannot for the life of me figure out what to do. If anyone has any idea on what I can do or resources I can look into, I'll be eternally grateful.

TL;DR I want my UILabel's background property in my custom cell (in a UITableView) to expand line by line with my text being typed out by my typing function, but I can't achieve this effect. How to get?

My StoryCell.swift's code for reference if you need:

import UIKit

class StoryCell: UITableViewCell {
    
    var typingTimer: Timer? // Add typingTimer property

    @IBOutlet weak var label: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }

    // Add startTypingAnimation method
    func startTypingAnimation(withText text: String) {
        label.text = &quot;&quot; // Clear existing text
        var charIndex = 0

        // Create a timer to simulate typing animation
        typingTimer?.invalidate() // this code line here is to make sure that no previous timers are running
        
        typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
            if charIndex &lt; text.count {
                let index = text.index(text.startIndex, offsetBy: charIndex) // The text.index(_, offsetBy:) method is used to obtain the index of a character in the string text. It takes two arguments: the starting index and an offset. The offset specifies the number of positions to move from the starting index.

                let nextChar = String(text[index]) // the character at the position specified by the offset value
                
                self.label.text?.append(nextChar)
                charIndex += 1
            } else {
                timer.invalidate()
            }
        }
    }
}

If you must know, the label's text comes from another .swift file which is why you won't see any text in this file.

答案1

得分: 1

我不确定为什么你的约束条件确切地没有起作用。我记得以前做过类似的事情,单元格会按预期自动调整大小。

无论如何,如果你使用自 iOS 14 起就存在的 UIListContentConfiguration API,单元格会自动调整大小。这允许你以各种方式自定义表视图单元格,而无需手动添加子视图。当然,它不如手工制作自己的单元格灵活,但它允许的自定义仍然相当丰富。还有相关的类,比如 UIBackgroundConfiguration

这是修改后使用 UIListContentConfigurationstartTypingAnimation

// 在 awakeFromNib 中,设置初始配置:
self.contentConfiguration = self.defaultContentConfiguration()

// ...

func startTypingAnimation(withText text: String) {
    var config = self.contentConfiguration as! UIListContentConfiguration
    config.text = ""
    self.contentConfiguration = config
    //label.text = ""
    var charIndex = 0

    typingTimer?.invalidate()
    
    typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
        if charIndex < text.count {
            let index = text.index(text.startIndex, offsetBy: charIndex)

            let nextChar = String(text[index]) // 位置由偏移值指定的位置上的字符
            
            var newConfig = self.contentConfiguration as? UIListContentConfiguration
            newConfig?.text?.append(nextChar)
            self.contentConfiguration = newConfig
            //self.label.text?.append(nextChar)
            charIndex += 1
        } else {
            timer.invalidate()
        }
    }
}
英文:

I'm not sure why exactly your constraints didn't work. I remember doing something similar before, and the cell automatically resizes as expected.

In any case, the cell does automatically resize if you use the UIListContentConfiguration API, which existed since iOS 14. This allows you customise table view cells in various ways, without manually adding the subviews yourself. Of course, it is less flexible than hand-crafting your own cells, but the customisation it allows is still quite a lot. There are also related classes like UIBackgroundConfiguration.

Here is startTypingAnimation, but changed to use UIListContentConfiguration:

// in awakeFromNib, set an initial configuration:
self.contentConfiguration = self.defaultContentConfiguration()

// ...

func startTypingAnimation(withText text: String) {
    var config = self.contentConfiguration as! UIListContentConfiguration
    config.text = &quot;&quot;
    self.contentConfiguration = config
    //label.text = &quot;&quot;
    var charIndex = 0

    typingTimer?.invalidate()
    
    typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
        if charIndex &lt; text.count {
            let index = text.index(text.startIndex, offsetBy: charIndex)

            let nextChar = String(text[index]) // the character at the position specified by the offset value
            
            var newConfig = self.contentConfiguration as? UIListContentConfiguration
            newConfig?.text?.append(nextChar)
            self.contentConfiguration = newConfig
            //self.label.text?.append(nextChar)
            charIndex += 1
        } else {
            timer.invalidate()
        }
    }
}

答案2

得分: 1

以下是您要翻译的内容:

当表视图布局其行/单元格时...

  • 是否实现了heightForRowAt indexPath
    • 使用该值
  • 否则,tableView.rowHeight是否设置为特定值?
    • 使用该值
  • 否则,根据其约束和数据,询问单元格的高度
    • 使用该值

一旦表格显示出来,它不再修改其行高

因此,如果您的代码以一种会改变其高度的方式更改了单元格的内容,我们必须通知控制器并让它重新布局单元格。

这通常是在单元格类中使用closure来完成的。

所以,假设您的单元格 xib 看起来像这样:

如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

我们可以将以下内容添加到您的单元格类中:

// closure 用于通知表视图控制器文本已更改
var textChanged: (() -> ())?

然后,当您的计时器向标签的文本添加新字符时:

self.label.text?.append(nextChar)
charIndex += 1
				
// 通知控制器文本已更改
self.textChanged?()

回到您的控制器类,在 cellForRowAt 中设置闭包:

cell.textChanged = { [weak self] in
	guard let self = self else { return }
		
	// 如果需要,这将更新行高度
	self.tableView.performBatchUpdates(nil)
}

这是一个完整的示例(假设您有 StoryCell.xib 如上所示)...

Cell 类

class StoryCell: UITableViewCell {
	
	// 用于通知表视图控制器文本已更改的闭包
	var textChanged: (() -> ())?
	
	var typingTimer: Timer? // 添加 typingTimer 属性
	
	@IBOutlet weak var label: UILabel!
	
	override func awakeFromNib() {
		super.awakeFromNib()
		// 初始化代码
	}
	
	override func setSelected(_ selected: Bool, animated: Bool) {
		super.setSelected(selected, animated: animated)
		// 配置所选单元格的视图
	}
	
	// 添加 startTypingAnimation 方法
	func startTypingAnimation(withText text: String) {
		label.text = "" // 清除现有文本
		var charIndex = 0
		
		// 创建一个计时器以模拟打字动画
		typingTimer?.invalidate() // 此代码行用于确保没有运行先前的计时器
		
		typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] timer in
			guard let self = self else {
				timer.invalidate()
				return
			}
			if charIndex < text.count {
				let index = text.index(text.startIndex, offsetBy: charIndex) // text.index(_, offsetBy:) 方法用于获取字符串 text 中字符的索引。它接受两个参数:起始索引和偏移量。偏移量指定从起始索引移动的位置。
				
				let nextChar = String(text[index]) // 由偏移值指定位置上的字符
				
				self.label.text?.append(nextChar)
				charIndex += 1
				
				// 通知控制器文本已更改
				self.textChanged?()
				
			} else {
				timer.invalidate()
			}
		}
	}
}

用于单元格文本的简单结构体

struct StoryStruct {
	var prompt: String = ""
	var story: String = ""
	var isStoryShowing: Bool = false
}

以及示例的视图控制器 - (将一个新的普通视图控制器分配给 StoryVC):

class StoryVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
	
	let tableView = UITableView()
	
	var myData: [StoryStruct] = [
		StoryStruct(prompt: "发生了什么?", story: "学习应用开发!"),
		StoryStruct(prompt: "什么是 UILabel?", story: "UILabel\n\n标签可以包含任意数量的文本,但 UILabel 可能会根据边界矩形的大小和您设置的属性来收缩、换行或截断文本。您可以控制标签中文本的字体、文本颜色、对齐方式、高亮和阴影。"),
		StoryStruct(prompt: "什么是 UIButton?", story: "UIButton\n\n按钮显示一个普通的样式按钮,可以具有标题、副标题、图像和其他外观属性。"),
		StoryStruct(prompt: "什么是 UISwitch?", story: "UISwitch\n\n开关显示一个元素,向用户显示给定值的布尔状态。\n\n通过点击控件,可以切换状态。"),
		StoryStruct(prompt: "接下来做什么?", story: "恭喜!您现在已经学会了有关开发 iPhone 应用程序的所有知识!"),
	]
	
	var activeRow: Int = 0

	override func viewDidLoad() {
		super.viewDidLoad()
		view.backgroundColor = .systemBackground
		
		tableView.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(tableView)
		
		let g = view.safeAreaLayoutGuide
		NSLayoutConstraint.activate([
			tableView.topAnchor.constraint(equalTo: g.topAnchor),
			tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
			tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
			tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
		])
		
		tableView.register(UINib(nibName: "StoryCell", bundle: nil), forCellReuseIdentifier: "StoryCell")
		tableView.dataSource = self
		tableView.delegate = self
		
	}

	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return myData.count
	}
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: "StoryCell", for: indexPath) as! StoryCell
		
		cell.selectionStyle = .none
		
		let ss: StoryStruct = myData[indexPath.row]
		
		if ss.isStoryShowing {
			cell.label.text = ss.story
		} else {
			cell.label.text = ss.prompt
		}
			
		cell.textChanged = { [weak self] in
			guard let self = self else {
英文:

When a table view lays out its rows/cells...

  • is heightForRowAt indexPath implemented?
    • use that value
  • else, is tableView.rowHeight set to a specific value?
    • use that value
  • else, ask the cell for its height (based on its constraints and data)
    • use that value

Once the table is displayed, it no longer modifies its row heights.

So, if your code changes the content of the cell in a way that its height will change, we have to inform the controller and let it re-layout the cell(s).

This is commonly done with a closure in the cell class.

So, assuming your cell xib looks something like this:

如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

We can add this to your cell class:

// closure to inform the table view controller that the text has changed
var textChanged: (() -&gt; ())?

then, when your timer adds a new character to the label's text:

self.label.text?.append(nextChar)
charIndex += 1
				
// inform the controller that the text has changed
self.textChanged?()

Back in your controller class, in cellForRowAt, we set the closure:

cell.textChanged = { [weak self] in
	guard let self = self else { return }
		
	// this will update the row height if needed
	self.tableView.performBatchUpdates(nil)
}

Here is a complete example (assuming you have StoryCell.xib as shown above)...

Cell Class

class StoryCell: UITableViewCell {
	
	// closure to inform the table view controller that the text has changed
	var textChanged: (() -&gt; ())?
	
	var typingTimer: Timer? // Add typingTimer property
	
	@IBOutlet weak var label: UILabel!
	
	override func awakeFromNib() {
		super.awakeFromNib()
		// Initialization code
	}
	
	override func setSelected(_ selected: Bool, animated: Bool) {
		super.setSelected(selected, animated: animated)
		// Configure the view for the selected state
	}
	
	// Add startTypingAnimation method
	func startTypingAnimation(withText text: String) {
		label.text = &quot;&quot; // Clear existing text
		var charIndex = 0
		
		// Create a timer to simulate typing animation
		typingTimer?.invalidate() // this code line here is to make sure that no previous timers are running
		
		typingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] timer in
			guard let self = self else {
				timer.invalidate()
				return
			}
			if charIndex &lt; text.count {
				let index = text.index(text.startIndex, offsetBy: charIndex) // The text.index(_, offsetBy:) method is used to obtain the index of a character in the string text. It takes two arguments: the starting index and an offset. The offset specifies the number of positions to move from the starting index.
				
				let nextChar = String(text[index]) // the character at the position specified by the offset value
				
				self.label.text?.append(nextChar)
				charIndex += 1
				
				// inform the controller that the text has changed
				self.textChanged?()
				
			} else {
				timer.invalidate()
			}
		}
	}
}

a simple struct for the cell text

struct StoryStruct {
	var prompt: String = &quot;&quot;
	var story: String = &quot;&quot;
	var isStoryShowing: Bool = false
}

and an example view controller - (assign a new, plain view controller to StoryVC):

class StoryVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
	
	let tableView = UITableView()
	
	var myData: [StoryStruct] = [
		StoryStruct(prompt: &quot;What&#39;s going on?&quot;, story: &quot;Learn App Development!&quot;),
		StoryStruct(prompt: &quot;What is a UILabel?&quot;, story: &quot;UILabel\n\nA label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label.&quot;),
		StoryStruct(prompt: &quot;What is a UIButton?&quot;, story: &quot;UIButton\n\nA button displays a plain styled button that can have a title, subtitle, image, and other appearance properties.&quot;),
		StoryStruct(prompt: &quot;What is a UISwitch?&quot;, story: &quot;UISwitch\n\nA switch displays an element that shows the user the boolean state of a given value.\n\nBy tapping the control, the state can be toggled.&quot;),
		StoryStruct(prompt: &quot;What next?&quot;, story: &quot;Congratulations! You&#39;ve now learned everything there is to know about developing iPhone Apps!&quot;),
	]
	
	var activeRow: Int = 0

	override func viewDidLoad() {
		super.viewDidLoad()
		view.backgroundColor = .systemBackground
		
		tableView.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(tableView)
		
		let g = view.safeAreaLayoutGuide
		NSLayoutConstraint.activate([
			tableView.topAnchor.constraint(equalTo: g.topAnchor),
			tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
			tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
			tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
		])
		
		tableView.register(UINib(nibName: &quot;StoryCell&quot;, bundle: nil), forCellReuseIdentifier: &quot;StoryCell&quot;)
		tableView.dataSource = self
		tableView.delegate = self
		
	}

	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
		return myData.count
	}
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: &quot;StoryCell&quot;, for: indexPath) as! StoryCell
		
		cell.selectionStyle = .none
		
		let ss: StoryStruct = myData[indexPath.row]
		
		if ss.isStoryShowing {
			cell.label.text = ss.story
		} else {
			cell.label.text = ss.prompt
		}
			
		cell.textChanged = { [weak self] in
			guard let self = self else { return }
			
			// this will update the row height if needed
			self.tableView.performBatchUpdates(nil)
		}
			
		return cell
	}

	func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
		if indexPath.row == activeRow {
			if let c = tableView.cellForRow(at: indexPath) as? StoryCell {
				c.startTypingAnimation(withText: myData[indexPath.row].story)
				myData[indexPath.row].isStoryShowing = true
			}
			activeRow += 1
		}
	}
}

When run, it will look like this:

如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

As you tap each row from top-to-bottom, your typing simulation will change the text in the row and it will expand:

如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

Note - you need to set the Content Mode on the label to Top Left:

如何在生成文本时扩展UITableViewCell的UILabel的背景属性?

otherwise, the text in the label will "bounce" as it is resized.

Keep in mind -- this is Example Code Only!!! to get you on your way.

huangapple
  • 本文由 发表于 2023年6月13日 11:17:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76461489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定