这是我用来实现此目的的代码:
import UIKit
class ViewController: UIViewController, UISearchBarDelegate {
var placeholderIndex = 0
var placeholderTexts = ["Text 1", "Text 2", "Text 3"]
var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar = UISearchBar()
searchBar.placeholder = "Search...." // Set initial placeholder
searchBar.searchBarStyle = .prominent
searchBar.sizeToFit()
searchBar.isTranslucent = true
searchBar.delegate = self
searchBar.backgroundImage = UIImage()
searchBar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(searchBar)
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(updatePlaceholder), userInfo: nil, repeats: true)
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
searchBar.heightAnchor.constraint(equalToConstant: 70)
])
}
@objc func updatePlaceholder() {
let nextPlaceholderLabel = UILabel(frame: CGRect(x: 0, y: -searchBar.frame.height, width: searchBar.frame.width, height: searchBar.frame.height))
nextPlaceholderLabel.text = placeholderTexts[placeholderIndex]
nextPlaceholderLabel.textAlignment = .center
nextPlaceholderLabel.font = searchBar.searchTextField.font
nextPlaceholderLabel.textColor = searchBar.searchTextField.textColor
searchBar.addSubview(nextPlaceholderLabel)
UIView.animate(withDuration: 0.5, animations: {
nextPlaceholderLabel.frame.origin.y = 0
nextPlaceholderLabel.alpha = 1.0
self.searchBar.layoutIfNeeded()
}) { _ in
self.searchBar.placeholder = self.placeholderTexts[self.placeholderIndex]
nextPlaceholderLabel.removeFromSuperview()
}
placeholderIndex = (placeholderIndex + 1) % placeholderTexts.count
}
}
函数 updatePlaceholder() 应该实现这个动画,但是我的结果是这样的:
我需要在这里做哪些改变? 预先感谢
有多种方法可以解决这个问题...这是一种...
您想要复制的效果是为各个单词设置动画。更改占位符文本时,当前文本会逐字向下动画并淡出,然后新文本会逐字向下动画(从上方)并淡入。
因此,当我们更新占位符文本时,我们可以:
所以我们可以看到发生了什么,我用红色勾勒出标签,它们还没有“淡出”。
再次,为了让我们可以看到发生了什么,我用蓝色勾勒出这些标签,并在开始时可见。 (它们最终将从 alpha-0 开始,然后“淡入”。)
最后一步是更新
.placeholder
文本属性,并删除所有动画标签。
这是一个简单的例子:
class ViewController: UIViewController, UISearchBarDelegate {
// start index at -1 so we get the first item -- [0] -- with the first increment
var placeholderIndex = -1
let placeholderTexts = [
"What would you like to do?",
"Check your Balance",
"Send money to Mom",
"Text 1",
"Text 2",
"Text 3",
]
var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar = UISearchBar()
searchBar.placeholder = "Search...." // Set initial placeholder
searchBar.searchBarStyle = .prominent
searchBar.isTranslucent = true
searchBar.delegate = self
searchBar.backgroundImage = UIImage()
searchBar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(searchBar)
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
searchBar.heightAnchor.constraint(equalToConstant: 70)
])
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(updatePlaceholder), userInfo: nil, repeats: true)
}
@objc func updatePlaceholder() {
// use these values for the placeholder label frame origin
// if we can't get find it via code
var baseX: CGFloat = 38.0
var baseY: CGFloat = 25.0
// try to find the placeholder label
// Apple did not make it public, so this is one approach
// note that if Apple changes the structure in the future, this may fail
var placeholderLabelFrame: CGRect = .zero
let tf: UITextField = searchBar.searchTextField
for subview in tf.subviews {
// Check if the subview is a UILabel and has the same text as the placeholder
if let label = subview as? UILabel, label.text == tf.placeholder {
placeholderLabelFrame = label.frame
}
}
if placeholderLabelFrame != .zero {
let cvtFrame = searchBar.searchTextField.convert(placeholderLabelFrame, to: searchBar)
baseX = cvtFrame.origin.x
baseY = cvtFrame.origin.y
}
// used to offset the labels vertically
var yOffset: CGFloat = 0.0
let hideIncrement: CGFloat = 2.0
let showIncrement: CGFloat = 8.0
// for positioning the labels
var currentX: CGFloat = 0
var toHideWords: [String] = []
var toShowWords: [String] = []
var toHideLabels: [UILabel] = []
var toShowLabels: [UILabel] = []
// array of Y-positions for the toHide labels to animate to
var targetY: [CGFloat] = []
let sbFont = searchBar.searchTextField.font
// split the current placeholder text into words
let hidePlaceholder: String = searchBar.placeholder ?? ""
toHideWords = hidePlaceholder.components(separatedBy: " ")
currentX = baseX
// create and position labels for each word
for word in toHideWords {
let wordLabel = UILabel()
wordLabel.text = word + " "
wordLabel.font = sbFont
wordLabel.textColor = .gray
wordLabel.sizeToFit()
wordLabel.frame.origin = .init(x: currentX, y: baseY)
currentX += wordLabel.frame.width
toHideLabels.append(wordLabel)
searchBar.addSubview(wordLabel)
}
// increment index into placeholderTexts
placeholderIndex = (placeholderIndex + 1) % placeholderTexts.count
// split the new placeholder text into words
let showPlaceholder: String = placeholderTexts[placeholderIndex]
toShowWords = showPlaceholder.components(separatedBy: " ")
currentX = baseX
// create and position labels for each word
for word in toShowWords {
let wordLabel = UILabel()
wordLabel.text = word + " "
wordLabel.font = sbFont
wordLabel.textColor = .gray
wordLabel.sizeToFit()
wordLabel.frame.origin = .init(x: currentX, y: baseY)
currentX += wordLabel.frame.width
toShowLabels.append(wordLabel)
searchBar.addSubview(wordLabel)
}
// we've added both sets of labels...
// so now we need to set the
// starting y-positions of the toShow labels
// and the
// ending y-positions of the toHide labels
// the toShow labels will be animated down *to* the baseY
// and faded in
yOffset = showIncrement
for wordLabel in toShowLabels {
wordLabel.alpha = 0.0
wordLabel.frame.origin.y = baseY - yOffset
yOffset += showIncrement
}
// the toHide labels will be animated down *from* the baseY
// and faded out
yOffset = baseY + hideIncrement
for wordLabel in toHideLabels {
wordLabel.alpha = 1.0
wordLabel.frame.origin.y = baseY
targetY.append(yOffset)
yOffset += hideIncrement
}
// clear the current placeholder text
self.searchBar.placeholder = ""
// we need to reverse the order of the toHide labels
// because we want the animation to start at the right-end
toHideLabels.reverse()
targetY.reverse()
var totalDur: Double = 0.0
var relStart: Double = 0.0
// all times in seconds (0.1 == 1/10th of a second, 0.05 = 1/20th, etc)
var hideSpeed: Double = 0.1
var hideInterval: Double = 0.05
// pause between hiding the "current" placeholder and showing the "new" placeholder
var pause: Double = 0.5
var showSpeed: Double = 0.2
var showInterval: Double = 0.1
let numToHide: Double = Double(toHideLabels.count)
let numToShow: Double = Double(toShowLabels.count)
// total duration in seconds
totalDur = (numToHide * hideSpeed) + pause + (numToShow * showSpeed)
// keyFrame start times and durations are relative to the total duration
// so convert them to percentages
hideSpeed /= totalDur
hideInterval /= totalDur
pause /= totalDur
showSpeed /= totalDur
showInterval /= totalDur
UIView.animateKeyframes(withDuration: totalDur, delay: 0, options: [.calculationModeLinear], animations: {
for (index, wordLabel) in toHideLabels.enumerated() {
UIView.addKeyframe(withRelativeStartTime: relStart, relativeDuration: hideSpeed, animations: {
wordLabel.alpha = 0.0
wordLabel.frame.origin.y = targetY[index]
})
relStart += hideInterval
}
relStart += pause
for wordLabel in toShowLabels {
UIView.addKeyframe(withRelativeStartTime: relStart, relativeDuration: showSpeed, animations: {
wordLabel.alpha = 1.0
wordLabel.frame.origin.y = baseY
})
relStart += showInterval
}
}, completion: { _ in
// set the new placeholder text
self.searchBar.placeholder = showPlaceholder
// remove all of the word labels
for wordLabel in toShowLabels {
wordLabel.removeFromSuperview()
}
for wordLabel in toHideLabels {
wordLabel.removeFromSuperview()
}
})
}
}
运行时看起来像这样: