在 scrollableStack 中处理选定的 UIView
Handle selected UIView in scrollableStack
我创建了一些标签。选项卡按预期在堆栈内水平滚动。
我正在尝试向选定的选项卡(请参见下图)添加底线(高度为 UIView = 2.0),并在选择新选项卡时为底线的过渡设置动画。
这是我的代码:
UIView.animate(withDuration: 0.5, animations: {
let center = viewSelected.center
let xPointToMove = center.x
self.animatedView.transform = CGAffineTransform(translationX: xPointToMove, y: 0.0)
})
问题是,如果用户点击第二个选项卡,animatedView 只会向右移动几个像素。我意识到动画发生后“viewSelected != animatedView”的center.x点。
感谢您对此的任何意见!
问题是您的“选项卡”视图是 UIStackView
的子视图...因此它们的坐标系(框架、中心等)是相对于它们在堆栈视图中的位置的。
您需要转换坐标。
这样试试:
// get a reference to the stack view holding viewSelected
// and a reference to the stack view's superView
guard let stackV = viewSelected.superview as? UIStackView,
let stackSV = stackV.superview
else {
return
}
// convert the frame of viewSelected from the stack view to its superView
let r = stackV.convert(viewSelected.frame, to: stackSV)
// animatedView center.x will be the converted frame's .midX
let xPointToMove = r.midX
UIView.animate(withDuration: 0.5, animations: {
// animate the view itself, instead of Transforming its layer
self.animatedView.center.x = xPointToMove
})
Edit - 您可能会发现使用自动布局约束比设置动画视图的框架更容易。此外,使用约束将有助于保持动画视图的正确定位和大小 if/when 整体视图更改大小。
下面是一个完整的示例:
class MyCustomTabView: UIView {
var selected: Bool = false {
didSet {
label.textColor = selected ? tintSelectedColor : tintNormalColor
imgView.tintColor = selected ? tintSelectedColor : tintNormalColor
backgroundColor = selected ? bkgSelectedColor : bkgNormalColor
}
}
let stack = UIStackView()
let imgView = UIImageView()
let label = UILabel()
let bkgNormalColor: UIColor = #colorLiteral(red: 0.9625813365, green: 0.9627193809, blue: 0.9625510573, alpha: 1)
let bkgSelectedColor: UIColor = #colorLiteral(red: 0.9008318782, green: 0.8487855792, blue: 0.9591421485, alpha: 1)
let tintNormalColor: UIColor = #colorLiteral(red: 0.4399604499, green: 0.4400276542, blue: 0.4399457574, alpha: 1)
let tintSelectedColor: UIColor = #colorLiteral(red: 0.3831990361, green: 0.0002554753446, blue: 0.9317755103, alpha: 1)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
stack.translatesAutoresizingMaskIntoConstraints = false
stack.spacing = 8
addSubview(stack)
stack.addArrangedSubview(imgView)
stack.addArrangedSubview(label)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
])
label.font = .boldSystemFont(ofSize: 14.0)
label.textColor = tintNormalColor
imgView.tintColor = tintNormalColor
backgroundColor = bkgNormalColor
}
}
class MyCustomTabBarView: UIView {
let stack = UIStackView()
let animatedView = UIView()
var avWidthConstraint: NSLayoutConstraint!
var avCenterXConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
animatedView.translatesAutoresizingMaskIntoConstraints = false
stack.translatesAutoresizingMaskIntoConstraints = false
addSubview(stack)
addSubview(animatedView)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: leadingAnchor),
stack.trailingAnchor.constraint(equalTo: trailingAnchor),
stack.heightAnchor.constraint(equalTo: heightAnchor),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
animatedView.bottomAnchor.constraint(equalTo: stack.bottomAnchor),
animatedView.heightAnchor.constraint(equalToConstant: 2.0),
])
animatedView.frame = CGRect(origin: .zero, size: CGSize(width: 100, height: 2))
let names: [String] = [
"ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX",
]
for (sName, i) in zip(names, Array(1...names.count)) {
let v = MyCustomTabView()
v.label.text = "TAB " + sName
v.imgView.image = UIImage(systemName: "\(i).circle")
let t = UITapGestureRecognizer(target: self, action: #selector(tabTapped(_:)))
v.addGestureRecognizer(t)
stack.addArrangedSubview(v)
}
// position animatedView at tab 0
if let v = stack.arrangedSubviews.first as? MyCustomTabView {
avWidthConstraint = animatedView.widthAnchor.constraint(equalTo: v.widthAnchor)
avCenterXConstraint = animatedView.centerXAnchor.constraint(equalTo: v.centerXAnchor)
avWidthConstraint.isActive = true
avCenterXConstraint.isActive = true
}
animatedView.backgroundColor = #colorLiteral(red: 0.3831990361, green: 0.0002554753446, blue: 0.9317755103, alpha: 1)
}
@objc func tabTapped(_ g: UITapGestureRecognizer) -> Void {
guard let viewSelected = g.view as? MyCustomTabView,
let n = stack.arrangedSubviews.firstIndex(of: viewSelected)
else {
return
}
selectTab(n)
}
// so we can select a tab programmatically from the controller
func selectTab(_ idx: Int) -> Void {
// make sure we're not trying to select a tab that doesn't exists
guard idx > -1, idx < stack.arrangedSubviews.count,
let viewSelected = stack.arrangedSubviews[idx] as? MyCustomTabView
else {
return
}
stack.arrangedSubviews.forEach { v in
if let vv = v as? MyCustomTabView {
vv.selected = vv == viewSelected
}
}
if avWidthConstraint != nil {
avWidthConstraint.isActive = false
avCenterXConstraint.isActive = false
}
avWidthConstraint = animatedView.widthAnchor.constraint(equalTo: viewSelected.widthAnchor)
avCenterXConstraint = animatedView.centerXAnchor.constraint(equalTo: viewSelected.centerXAnchor)
avWidthConstraint.isActive = true
avCenterXConstraint.isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.layoutIfNeeded()
})
}
}
class qCheckTVVC: UIViewController {
let scrollView = UIScrollView()
let myTabsView = MyCustomTabBarView()
override func viewDidLoad() {
super.viewDidLoad()
let g = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
scrollView.translatesAutoresizingMaskIntoConstraints = false
myTabsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.addSubview(myTabsView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
scrollView.heightAnchor.constraint(equalToConstant: 40.0),
myTabsView.topAnchor.constraint(equalTo: contentG.topAnchor),
myTabsView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
myTabsView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
myTabsView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
myTabsView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
scrollView.showsHorizontalScrollIndicator = false
}
}
我创建了一些标签。选项卡按预期在堆栈内水平滚动。
我正在尝试向选定的选项卡(请参见下图)添加底线(高度为 UIView = 2.0),并在选择新选项卡时为底线的过渡设置动画。
这是我的代码:
UIView.animate(withDuration: 0.5, animations: {
let center = viewSelected.center
let xPointToMove = center.x
self.animatedView.transform = CGAffineTransform(translationX: xPointToMove, y: 0.0)
})
问题是,如果用户点击第二个选项卡,animatedView 只会向右移动几个像素。我意识到动画发生后“viewSelected != animatedView”的center.x点。
感谢您对此的任何意见!
问题是您的“选项卡”视图是 UIStackView
的子视图...因此它们的坐标系(框架、中心等)是相对于它们在堆栈视图中的位置的。
您需要转换坐标。
这样试试:
// get a reference to the stack view holding viewSelected
// and a reference to the stack view's superView
guard let stackV = viewSelected.superview as? UIStackView,
let stackSV = stackV.superview
else {
return
}
// convert the frame of viewSelected from the stack view to its superView
let r = stackV.convert(viewSelected.frame, to: stackSV)
// animatedView center.x will be the converted frame's .midX
let xPointToMove = r.midX
UIView.animate(withDuration: 0.5, animations: {
// animate the view itself, instead of Transforming its layer
self.animatedView.center.x = xPointToMove
})
Edit - 您可能会发现使用自动布局约束比设置动画视图的框架更容易。此外,使用约束将有助于保持动画视图的正确定位和大小 if/when 整体视图更改大小。
下面是一个完整的示例:
class MyCustomTabView: UIView {
var selected: Bool = false {
didSet {
label.textColor = selected ? tintSelectedColor : tintNormalColor
imgView.tintColor = selected ? tintSelectedColor : tintNormalColor
backgroundColor = selected ? bkgSelectedColor : bkgNormalColor
}
}
let stack = UIStackView()
let imgView = UIImageView()
let label = UILabel()
let bkgNormalColor: UIColor = #colorLiteral(red: 0.9625813365, green: 0.9627193809, blue: 0.9625510573, alpha: 1)
let bkgSelectedColor: UIColor = #colorLiteral(red: 0.9008318782, green: 0.8487855792, blue: 0.9591421485, alpha: 1)
let tintNormalColor: UIColor = #colorLiteral(red: 0.4399604499, green: 0.4400276542, blue: 0.4399457574, alpha: 1)
let tintSelectedColor: UIColor = #colorLiteral(red: 0.3831990361, green: 0.0002554753446, blue: 0.9317755103, alpha: 1)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
stack.translatesAutoresizingMaskIntoConstraints = false
stack.spacing = 8
addSubview(stack)
stack.addArrangedSubview(imgView)
stack.addArrangedSubview(label)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
])
label.font = .boldSystemFont(ofSize: 14.0)
label.textColor = tintNormalColor
imgView.tintColor = tintNormalColor
backgroundColor = bkgNormalColor
}
}
class MyCustomTabBarView: UIView {
let stack = UIStackView()
let animatedView = UIView()
var avWidthConstraint: NSLayoutConstraint!
var avCenterXConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
animatedView.translatesAutoresizingMaskIntoConstraints = false
stack.translatesAutoresizingMaskIntoConstraints = false
addSubview(stack)
addSubview(animatedView)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: leadingAnchor),
stack.trailingAnchor.constraint(equalTo: trailingAnchor),
stack.heightAnchor.constraint(equalTo: heightAnchor),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
animatedView.bottomAnchor.constraint(equalTo: stack.bottomAnchor),
animatedView.heightAnchor.constraint(equalToConstant: 2.0),
])
animatedView.frame = CGRect(origin: .zero, size: CGSize(width: 100, height: 2))
let names: [String] = [
"ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX",
]
for (sName, i) in zip(names, Array(1...names.count)) {
let v = MyCustomTabView()
v.label.text = "TAB " + sName
v.imgView.image = UIImage(systemName: "\(i).circle")
let t = UITapGestureRecognizer(target: self, action: #selector(tabTapped(_:)))
v.addGestureRecognizer(t)
stack.addArrangedSubview(v)
}
// position animatedView at tab 0
if let v = stack.arrangedSubviews.first as? MyCustomTabView {
avWidthConstraint = animatedView.widthAnchor.constraint(equalTo: v.widthAnchor)
avCenterXConstraint = animatedView.centerXAnchor.constraint(equalTo: v.centerXAnchor)
avWidthConstraint.isActive = true
avCenterXConstraint.isActive = true
}
animatedView.backgroundColor = #colorLiteral(red: 0.3831990361, green: 0.0002554753446, blue: 0.9317755103, alpha: 1)
}
@objc func tabTapped(_ g: UITapGestureRecognizer) -> Void {
guard let viewSelected = g.view as? MyCustomTabView,
let n = stack.arrangedSubviews.firstIndex(of: viewSelected)
else {
return
}
selectTab(n)
}
// so we can select a tab programmatically from the controller
func selectTab(_ idx: Int) -> Void {
// make sure we're not trying to select a tab that doesn't exists
guard idx > -1, idx < stack.arrangedSubviews.count,
let viewSelected = stack.arrangedSubviews[idx] as? MyCustomTabView
else {
return
}
stack.arrangedSubviews.forEach { v in
if let vv = v as? MyCustomTabView {
vv.selected = vv == viewSelected
}
}
if avWidthConstraint != nil {
avWidthConstraint.isActive = false
avCenterXConstraint.isActive = false
}
avWidthConstraint = animatedView.widthAnchor.constraint(equalTo: viewSelected.widthAnchor)
avCenterXConstraint = animatedView.centerXAnchor.constraint(equalTo: viewSelected.centerXAnchor)
avWidthConstraint.isActive = true
avCenterXConstraint.isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.layoutIfNeeded()
})
}
}
class qCheckTVVC: UIViewController {
let scrollView = UIScrollView()
let myTabsView = MyCustomTabBarView()
override func viewDidLoad() {
super.viewDidLoad()
let g = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
scrollView.translatesAutoresizingMaskIntoConstraints = false
myTabsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.addSubview(myTabsView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
scrollView.heightAnchor.constraint(equalToConstant: 40.0),
myTabsView.topAnchor.constraint(equalTo: contentG.topAnchor),
myTabsView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
myTabsView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
myTabsView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
myTabsView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
scrollView.showsHorizontalScrollIndicator = false
}
}