如何将 UIView 置于其他 UIView 之上?
How to place UIView on top of other UIView?
现在我有一个 collectionView,每个单元格都包含一个水平 stackView。 stackView 填充了一系列 UIView(矩形),每个月的每一天都有一个 - 每个单元格对应一个月。我像这样填充堆栈视图:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionView {
...
return cell
} else if collectionView == self.timeline {
let index = indexPath.row
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: timelineMonthCellReuseIdentifier, for: indexPath) as! SNTimelineMonthViewCell
let firstPost = posts.first?.timeStamp
let month = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
print(dateFormatter.string(from: month!),dateFormatter.string(from: firstPost!),"month diff")
for post in posts {
print(post.timeStamp, "month diff")
}
cell.monthLabel.text = dateFormatter.string(from: month!)
cell.monthLabel.textAlignment = .center
if let start = month?.startOfMonth(), let end = month?.endOfMonth(), let stackView = cell.dayTicks {
var date = start
while date <= end {
let line = UIView()
if posts.contains(where: { Calendar.current.isDate(date, inSameDayAs: [=10=].timeStamp) }) {
line.backgroundColor = UIColor(red:0.15, green:0.67, blue:0.93, alpha:1.0)
let tapGuesture = UITapGestureRecognizer(target: self, action: #selector (self.tapBar (_:)))
line.isUserInteractionEnabled = true
line.addGestureRecognizer(tapGuesture)
self.dayTicks[date] = line
} else {
line.backgroundColor = UIColor.clear
}
stackView.addArrangedSubview(line)
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
}
return cell
} else {
preconditionFailure("Unknown collection view!")
}
}
然后,当用户停止滚动不同的集合视图时,我想在 dayTick 的顶部添加一个名为 arrowView 的子视图(参见上面的 stackView 的子视图如何填充 self.dayTicks)。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentIndex = self.collectionView.contentOffset.x / self.collectionView.frame.size.width
let post = posts[Int(currentIndex)]
for (_,tick) in self.dayTicks {
tick.subviews.forEach({ [=11=].removeFromSuperview() })
}
let day = Calendar.current.startOfDay(for: post.timeStamp)
let tick = self.dayTicks[day]
let arrow = UIImage(named:"Tracer Pin")
let arrowView = UIImageView(image: arrow)
// arrowView.clipsToBounds = false
print((tick?.frame.origin)!,"tick origin")
// arrowView.frame.origin = (tick?.frame.origin)!
// arrowView.frame.size.width = 100
// arrowView.frame.size.height = 100
tick?.addSubview(arrowView)
}
这种作品看起来像这样:
添加了红色矩形,但它出现在日标记的右侧,并且显示为细长矩形。实际上,引用的 Tracer Pin 图片如下所示:
这至少是红色的来源,但正如您所看到的那样,它的拉伸很奇怪,并且剪裁了不在矩形 UIView 中的所有内容 space。
现在请注意,我注释掉了设置 arrowView 的大小和原点以及将 clipToBounds 设置为 false 的 4 行。当我取消注释这些行时 - arrowView 根本不显示,所以我一定是做错了。我想要的是显示这样的东西:
我怎么能把它直接放在上面呢?
arrowView 的高度看起来是固定的。会不会是红色三角部分在另一个视图下?
Debug View Hierarchy
单击右侧第二个图标的调试视图层次结构 - 它看起来像 3 个矩形。检查是否有整张图片。
另一个角度可能是用 CALayer 来做这个。这里有一些线索(从另一个项目中截取)可以帮助您找到解决方案:
@IBInspectable open var slideIndicatorThickness: CGFloat = 5.0 {
didSet {
if slideIndicator != nil { slideIndicator.removeFromSuperlayer() }
let slideLayer = CALayer()
let theOrigin = CGPoint(x: bounds.origin.x, y: bounds.origin.y)
let theSize = CGSize(width: CGFloat(3.0), height: CGFloat(10.0)
slideLayer.frame = CGRect(origin: theOrigin, size: theSize)
slideLayer.backgroundColor = UIColor.orange.cgColor
slideIndicator = slideLayer
layer.addSublayer(slideIndicator)
}
}
fileprivate var slideIndicator: CALayer!
fileprivate func updateIndicator() {
// ..
// Somehow figure out new frame, based on stack view's frame.
slideIndicator.frame.origin.x = newOrigin
}
您可能必须在 UIStackView 的子类或您自己的自定义视图(它是 UIStackView 的包装器)上实现它。
我最终使用了 CALayer - 感谢@ouni 的建议出于某种原因,CALayer 似乎直接在视图之上绘制,而子视图则没有。一个关键是取消选中 collectionView 单元格本身(而不是子视图)上的“剪辑到边界框”——这样我就可以在集合视图单元格之外绘制箭头的喇叭形底部:
我的代码如下所示:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == self.collectionView {
print("is collection view")
print(scrollView,"collection view")
let currentIndex = self.collectionView.contentOffset.x / self.collectionView.frame.size.width
let post = posts[Int(currentIndex)]
for (_,tick) in self.dayTicks {
tick.layer.sublayers = nil
}
let day = Calendar.current.startOfDay(for: post.timeStamp)
let tick = self.dayTicks[day]
let arrowLayer = CAShapeLayer()
let path = UIBezierPath()
let start_x = (tick?.bounds.origin.x)!
let start_y = (tick?.bounds.minY)!
let top_width = (tick?.bounds.width)!
let tick_height = (tick?.bounds.height)!
let tip_height = CGFloat(10)
let tip_flare = CGFloat(10)
path.move(to: CGPoint(x: start_x, y: start_y))
path.addLine(to: CGPoint(x: start_x + top_width,y: start_y))
path.addLine(to: CGPoint(x: start_x + top_width,y: start_y + tick_height))
path.addLine(to: CGPoint(x: start_x + top_width + tip_flare,y: start_y+tick_height+tip_height))
path.addLine(to: CGPoint(x: start_x - tip_flare,y: start_y + tick_height + tip_height))
path.addLine(to: CGPoint(x: start_x,y: start_y+tick_height))
path.close()
arrowLayer.path = path.cgPath
arrowLayer.fillColor = UIColor(red:0.99, green:0.13, blue:0.25, alpha:1.0).cgColor
tick?.layer.addSublayer(arrowLayer)
} else {
print(scrollView, "timeline collection view")
}
}
这在子视图顶部绘制了漂亮的箭头。
现在我有一个 collectionView,每个单元格都包含一个水平 stackView。 stackView 填充了一系列 UIView(矩形),每个月的每一天都有一个 - 每个单元格对应一个月。我像这样填充堆栈视图:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionView {
...
return cell
} else if collectionView == self.timeline {
let index = indexPath.row
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: timelineMonthCellReuseIdentifier, for: indexPath) as! SNTimelineMonthViewCell
let firstPost = posts.first?.timeStamp
let month = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
print(dateFormatter.string(from: month!),dateFormatter.string(from: firstPost!),"month diff")
for post in posts {
print(post.timeStamp, "month diff")
}
cell.monthLabel.text = dateFormatter.string(from: month!)
cell.monthLabel.textAlignment = .center
if let start = month?.startOfMonth(), let end = month?.endOfMonth(), let stackView = cell.dayTicks {
var date = start
while date <= end {
let line = UIView()
if posts.contains(where: { Calendar.current.isDate(date, inSameDayAs: [=10=].timeStamp) }) {
line.backgroundColor = UIColor(red:0.15, green:0.67, blue:0.93, alpha:1.0)
let tapGuesture = UITapGestureRecognizer(target: self, action: #selector (self.tapBar (_:)))
line.isUserInteractionEnabled = true
line.addGestureRecognizer(tapGuesture)
self.dayTicks[date] = line
} else {
line.backgroundColor = UIColor.clear
}
stackView.addArrangedSubview(line)
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
}
return cell
} else {
preconditionFailure("Unknown collection view!")
}
}
然后,当用户停止滚动不同的集合视图时,我想在 dayTick 的顶部添加一个名为 arrowView 的子视图(参见上面的 stackView 的子视图如何填充 self.dayTicks)。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentIndex = self.collectionView.contentOffset.x / self.collectionView.frame.size.width
let post = posts[Int(currentIndex)]
for (_,tick) in self.dayTicks {
tick.subviews.forEach({ [=11=].removeFromSuperview() })
}
let day = Calendar.current.startOfDay(for: post.timeStamp)
let tick = self.dayTicks[day]
let arrow = UIImage(named:"Tracer Pin")
let arrowView = UIImageView(image: arrow)
// arrowView.clipsToBounds = false
print((tick?.frame.origin)!,"tick origin")
// arrowView.frame.origin = (tick?.frame.origin)!
// arrowView.frame.size.width = 100
// arrowView.frame.size.height = 100
tick?.addSubview(arrowView)
}
这种作品看起来像这样:
添加了红色矩形,但它出现在日标记的右侧,并且显示为细长矩形。实际上,引用的 Tracer Pin 图片如下所示:
这至少是红色的来源,但正如您所看到的那样,它的拉伸很奇怪,并且剪裁了不在矩形 UIView 中的所有内容 space。
现在请注意,我注释掉了设置 arrowView 的大小和原点以及将 clipToBounds 设置为 false 的 4 行。当我取消注释这些行时 - arrowView 根本不显示,所以我一定是做错了。我想要的是显示这样的东西:
我怎么能把它直接放在上面呢?
arrowView 的高度看起来是固定的。会不会是红色三角部分在另一个视图下?
Debug View Hierarchy
单击右侧第二个图标的调试视图层次结构 - 它看起来像 3 个矩形。检查是否有整张图片。
另一个角度可能是用 CALayer 来做这个。这里有一些线索(从另一个项目中截取)可以帮助您找到解决方案:
@IBInspectable open var slideIndicatorThickness: CGFloat = 5.0 {
didSet {
if slideIndicator != nil { slideIndicator.removeFromSuperlayer() }
let slideLayer = CALayer()
let theOrigin = CGPoint(x: bounds.origin.x, y: bounds.origin.y)
let theSize = CGSize(width: CGFloat(3.0), height: CGFloat(10.0)
slideLayer.frame = CGRect(origin: theOrigin, size: theSize)
slideLayer.backgroundColor = UIColor.orange.cgColor
slideIndicator = slideLayer
layer.addSublayer(slideIndicator)
}
}
fileprivate var slideIndicator: CALayer!
fileprivate func updateIndicator() {
// ..
// Somehow figure out new frame, based on stack view's frame.
slideIndicator.frame.origin.x = newOrigin
}
您可能必须在 UIStackView 的子类或您自己的自定义视图(它是 UIStackView 的包装器)上实现它。
我最终使用了 CALayer - 感谢@ouni 的建议出于某种原因,CALayer 似乎直接在视图之上绘制,而子视图则没有。一个关键是取消选中 collectionView 单元格本身(而不是子视图)上的“剪辑到边界框”——这样我就可以在集合视图单元格之外绘制箭头的喇叭形底部:
我的代码如下所示:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == self.collectionView {
print("is collection view")
print(scrollView,"collection view")
let currentIndex = self.collectionView.contentOffset.x / self.collectionView.frame.size.width
let post = posts[Int(currentIndex)]
for (_,tick) in self.dayTicks {
tick.layer.sublayers = nil
}
let day = Calendar.current.startOfDay(for: post.timeStamp)
let tick = self.dayTicks[day]
let arrowLayer = CAShapeLayer()
let path = UIBezierPath()
let start_x = (tick?.bounds.origin.x)!
let start_y = (tick?.bounds.minY)!
let top_width = (tick?.bounds.width)!
let tick_height = (tick?.bounds.height)!
let tip_height = CGFloat(10)
let tip_flare = CGFloat(10)
path.move(to: CGPoint(x: start_x, y: start_y))
path.addLine(to: CGPoint(x: start_x + top_width,y: start_y))
path.addLine(to: CGPoint(x: start_x + top_width,y: start_y + tick_height))
path.addLine(to: CGPoint(x: start_x + top_width + tip_flare,y: start_y+tick_height+tip_height))
path.addLine(to: CGPoint(x: start_x - tip_flare,y: start_y + tick_height + tip_height))
path.addLine(to: CGPoint(x: start_x,y: start_y+tick_height))
path.close()
arrowLayer.path = path.cgPath
arrowLayer.fillColor = UIColor(red:0.99, green:0.13, blue:0.25, alpha:1.0).cgColor
tick?.layer.addSublayer(arrowLayer)
} else {
print(scrollView, "timeline collection view")
}
}
这在子视图顶部绘制了漂亮的箭头。