- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我想知道在我正在构建的 iOS 应用程序上创建无限网格的最佳方法。我使用 iPhone 上的内部硬件来收集真实世界的数据并构建向量。我想直观地表示此网格上的矢量数据,有点像图形。每个向量都表示为一条线,每个新向量都附加到该网格上的前一个向量。我正在构建应用程序,以便它可以运行用户想要的任何时间(从几分钟到几小时)。我遇到的唯一问题是如何从这个网格开始。我想如果用户运行应用程序几个小时,应用程序上的行会变得很长,因此网格需要适应它并以这种方式“无限”。
无限是指用户可以在网格上向上、向下、向左或向右滑动,并且构成网格的线条永无止境。无论他们在哪里滑动或滑动多长时间,屏幕上总会有一个网格。如前所述,网格还应具有图形的属性。我希望得到接近于此的东西:https://bl.ocks.org/mbostock/6123708 , 但这张图截断了。
我做了一些研究,大多数关于网格的问题都来自 SpriteKit 框架。但我不知道运行游戏引擎是否是最好的解决方案。我想使用核心图形,但如果无法在该框架上执行此操作,我可以考虑任何其他建议。任何关于从哪里开始的帮助都将不胜感激!
最佳答案
我创建了一个示例项目来说明需要做什么。可以在以下位置找到代码:https://github.com/ekscrypto/Infinite-Grid-Swift
本质上,您从一个非常简单的 UIScrollView 开始,在其中分配一个“引用” View ,该 View 最初将成为 (0,0) 点。然后,您在引用 View 和 ScrollView 内容边缘之间设置了大得离谱的距离(足以让用户不可能不停地滚动)并调整 contentOffset 以便您的 View 适合 ScrollView 的中间。
然后您必须观察 scrollview 的 contentOffset 并计算出每侧需要多少瓦片才能填满屏幕以及更多,这样当用户滚动时总有内容可以显示。这可以设置为任意数量的图 block ,但请注意保持合理,因为您的图 block 可能会消耗内存。我发现 1 个全屏宽度/高度足以满足最快的手动滚动。
当用户滚动时,将调用 contentOffset 观察器,允许您根据需要添加或删除 View 。
当 ScrollView 完成动画后,您将需要重置引用点,以免用完 contentOffset 以供使用。
假设一个相对简单的“GridTile”类,将被实例化以填充网格:
protocol GridTileDataSource {
func contentView(for: GridTile) -> UIView?
}
class GridTile: UIView {
let coordinates: (Int, Int)
private let dataSource: GridTileDataSource
// Custom initializer
init(frame: CGRect, coordinates: (Int, Int), dataSource: GridTileDataSource) {
self.coordinates = coordinates
self.dataSource = dataSource
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.isOpaque = false
}
// Unused, not supporting Xib/Storyboard
required init?(coder aDecoder: NSCoder) {
return nil
}
override func draw(_ rect: CGRect) {
super.draw(rect)
populateWithContent()
}
private func populateWithContent() {
if self.subviews.count == 0,
let subview = dataSource.contentView(for: self) {
subview.frame = self.bounds
self.addSubview(subview)
}
}
}
然后从相对简单的 UIView/UIScrollView 设置开始:
您可以这样创建 GridView 机制:
class GridView: UIView {
@IBOutlet weak var hostScrollView: UIScrollView?
@IBOutlet weak var topConstraint: NSLayoutConstraint?
@IBOutlet weak var bottomConstraint: NSLayoutConstraint?
@IBOutlet weak var leftConstraint: NSLayoutConstraint?
@IBOutlet weak var rightConstraint: NSLayoutConstraint?
private(set) var allocatedTiles: [GridTile] = []
private(set) var referenceCoordinates: (Int, Int) = (0,0)
private(set) var tileSize: CGFloat = 0.0
private(set) var observingScrollview: Bool = false
private(set) var centerCoordinates: (Int, Int) = (Int.max, Int.max)
deinit {
if observingScrollview {
hostScrollView?.removeObserver(self, forKeyPath: "contentOffset")
}
}
func populateGrid(size tileSize: CGFloat, center: (Int, Int)) {
clearGrid()
self.referenceCoordinates = center
self.tileSize = tileSize
observeScrollview()
adjustScrollviewInsets()
}
private func clearGrid() {
for tile in allocatedTiles {
tile.removeFromSuperview()
}
allocatedTiles.removeAll()
}
private func observeScrollview() {
guard observingScrollview == false,
let scrollview = hostScrollView
else { return }
scrollview.delegate = self
scrollview.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
observingScrollview = true
}
private func adjustScrollviewInsets() {
guard let scrollview = hostScrollView else { return }
// maximum continous user scroll before hitting the scrollview edge
// set this to something small (~3000) to observe the scrollview indicator resetting to middle
let arbitraryLargeOffset: CGFloat = 10000000.0
topConstraint?.constant = arbitraryLargeOffset
bottomConstraint?.constant = arbitraryLargeOffset
leftConstraint?.constant = arbitraryLargeOffset
rightConstraint?.constant = arbitraryLargeOffset
scrollview.layoutIfNeeded()
let xOffset = arbitraryLargeOffset - ((scrollview.frame.size.width - self.frame.size.width) * 0.5)
let yOffset = arbitraryLargeOffset - ((scrollview.frame.size.height - self.frame.size.height) * 0.5)
scrollview.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: false)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let scrollview = object as? UIScrollView else { return }
adjustGrid(for: scrollview)
}
private func adjustGrid(for scrollview: UIScrollView) {
let center = computedCenterCoordinates(scrollview)
guard center != centerCoordinates else { return }
self.centerCoordinates = center
//print("center is now at coordinates: \(center)")
// pre-allocate views past the bounds of the visible scrollview so when user
// drags the view, even super-quick, there is content to show
let xCutoff = Int(((scrollview.frame.size.width * 1.5) / tileSize).rounded(.up))
let yCutoff = Int(((scrollview.frame.size.height * 1.5) / tileSize).rounded(.up))
let lowerX = center.0 - xCutoff
let upperX = center.0 + xCutoff
let lowerY = center.1 - yCutoff
let upperY = center.1 + yCutoff
clearGridOutsideBounds(lowerX: lowerX, upperX: upperX, lowerY: lowerY, upperY: upperY)
populateGridInBounds(lowerX: lowerX, upperX: upperX, lowerY: lowerY, upperY: upperY)
}
private func computedCenterCoordinates(_ scrollview: UIScrollView) -> (Int, Int) {
guard tileSize > 0 else { return centerCoordinates }
let contentOffset = scrollview.contentOffset
let scrollviewSize = scrollview.frame.size
let xOffset = -(self.center.x - (contentOffset.x + scrollviewSize.width * 0.5))
let yOffset = -(self.center.y - (contentOffset.y + scrollviewSize.height * 0.5))
let xIntOffset = Int((xOffset / tileSize).rounded())
let yIntOffset = Int((yOffset / tileSize).rounded())
return (xIntOffset + referenceCoordinates.0, yIntOffset + referenceCoordinates.1)
}
private func clearGridOutsideBounds(lowerX: Int, upperX: Int, lowerY: Int, upperY: Int) {
let tilesToProcess = allocatedTiles
for tile in tilesToProcess {
let tileX = tile.coordinates.0
let tileY = tile.coordinates.1
if tileX < lowerX || tileX > upperX || tileY < lowerY || tileY > upperY {
// print("Deallocating grid tile: \(tile.coordinates)")
tile.removeFromSuperview()
if let index = allocatedTiles.index(of: tile) {
allocatedTiles.remove(at: index)
}
}
}
}
private func populateGridInBounds(lowerX: Int, upperX: Int, lowerY: Int, upperY: Int) {
guard upperX > lowerX, upperY > lowerY else { return }
var coordX = lowerX
while coordX <= upperX {
var coordY = lowerY
while coordY <= upperY {
allocateTile(at: (coordX, coordY))
coordY += 1
}
coordX += 1
}
}
private func allocateTile(at tileCoordinates: (Int, Int)) {
guard existingTile(at: tileCoordinates) == nil else { return }
// print("Allocating grid tile: \(tileCoordinates)")
let tile = GridTile(frame: frameForTile(at: tileCoordinates),
coordinates: tileCoordinates,
dataSource: self)
allocatedTiles.append(tile)
self.addSubview(tile)
}
private func existingTile(at coordinates: (Int, Int)) -> GridTile? {
for tile in allocatedTiles where tile.coordinates == coordinates {
return tile
}
return nil
}
private func frameForTile(at coordinates: (Int, Int)) -> CGRect {
let xIntOffset = coordinates.0 - referenceCoordinates.0
let yIntOffset = coordinates.1 - referenceCoordinates.1
let xOffset = self.bounds.size.width * 0.5 + (tileSize * (CGFloat(xIntOffset) - 0.5))
let yOffset = self.bounds.size.height * 0.5 + (tileSize * (CGFloat(yIntOffset) - 0.5))
return CGRect(x: xOffset, y: yOffset, width: tileSize, height: tileSize)
}
// readjustOffsets() should only be called when the scrollview is not animating to
// avoid any jerky movement.
private func readjustOffsets() {
guard
centerCoordinates != referenceCoordinates,
let scrollview = hostScrollView,
tileSize > 0
else { return }
let xOffset = CGFloat(centerCoordinates.0 - referenceCoordinates.0) * tileSize
let yOffset = CGFloat(centerCoordinates.1 - referenceCoordinates.1) * tileSize
referenceCoordinates = centerCoordinates
for tile in allocatedTiles {
var frame = tile.frame
frame.origin.x -= xOffset
frame.origin.y -= yOffset
tile.frame = frame
}
var newContentOffset = scrollview.contentOffset
newContentOffset.x -= xOffset
newContentOffset.y -= yOffset
scrollview.setContentOffset(newContentOffset, animated: false)
}
}
extension GridView: UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
guard decelerate == false else { return }
self.readjustOffsets()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.readjustOffsets()
}
}
extension GridView: GridTileDataSource {
// This is where you would provide the content to put in the tiles, could be
// maps, images, whatever. In this case went with a simple label containing the coordinates
internal func contentView(for tile: GridTile) -> UIView? {
let placeholderLabel = UILabel(frame: tile.bounds)
let coordinates = tile.coordinates
placeholderLabel.text = "\(coordinates.0, coordinates.1)"
placeholderLabel.textColor = UIColor.blue
placeholderLabel.textAlignment = .center
return placeholderLabel
}
}
然后剩下的就是通过指定网格大小和要使用的初始坐标来启动您的 GridView:
class ViewController: UIViewController {
@IBOutlet weak var gridView: GridView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
gridView?.populateGrid(size: 150.0, center: (0,0))
}
}
这就是无限网格。
干杯,祝你好运!
关于ios - 在 iOS 中绘制无限网格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51544588/
IO 设备如何知道属于它的内存中的值在memory mapped IO 中发生了变化? ? 例如,假设内存地址 0 专用于保存 VGA 设备的背景颜色。当我们更改 memory[0] 中的值时,VGA
我目前正在开发一个使用Facebook sdk登录(通过FBLoginView)的iOS应用。 一切正常,除了那些拥有较旧版本的facebook的人。 当他们按下“使用Facebook登录”按钮时,他
假设我有: this - is an - example - with some - dashesNSRange将使用`rangeOfString:@“-”拾取“-”的第一个实例,但是如果我只想要最后
Card.io SDK提供以下详细信息: 卡号,有效期,月份,年份,CVV和邮政编码。 如何从此SDK获取国家名称。 - (void)userDidProvideCreditCardInfo:(Car
iOS 应用程序如何从网络服务下载图片并在安装过程中将它们安装到用户的 iOS 设备上?可能吗? 最佳答案 您无法控制应用在用户设备上的安装,因此无法在安装过程中下载其他数据。 只需在安装后首次启动应
我曾经开发过一款企业版 iOS 产品,我们公司曾将其出售给大型企业,供他们的员工使用。 该应用程序通过 AppStore 提供,企业用户获得了公司特定的配置文件(包含应用程序配置文件)以启用他们有权使
我正在尝试将 Card.io SDK 集成到我的 iOS 应用程序中。我想为 CardIO ui 做一个简单的本地化,如更改取消按钮标题或“在此保留信用卡”提示文本。 我在 github 上找到了这个
我正在使用 CardIOView 和 CardIOViewDelegate 类,没有可以设置为 YES 的 BOOL 来扫描 collectCardholderName。我可以看到它在 CardIOP
我有一个集成了通话工具包的 voip 应用程序。每次我从我的 voip 应用程序调用时,都会在 native 电话应用程序中创建一个新的最近通话记录。我在 voip 应用程序中也有自定义联系人(电话应
iOS 应用程序如何知道应用程序打开时屏幕上是否已经有键盘?应用程序运行后,它可以接收键盘显示/隐藏通知。但是,如果应用程序在分屏模式下作为辅助应用程序打开,而主应用程序已经显示键盘,则辅助应用程序不
我在模拟器中收到以下错误: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedIm
如 Apple 文档所示,可以通过 EAAccessory Framework 与经过认证的配件(由 Apple 认证)进行通信。但是我有点困惑,因为一些帖子告诉我它也可以通过 CoreBluetoo
尽管现在的调试器已经很不错了,但有时找出应用程序中正在发生的事情的最好方法仍然是古老的 NSLog。当您连接到计算机时,这样做很容易; Xcode 会帮助弹出日志查看器面板,然后就可以了。当您不在办公
在我的 iOS 应用程序中,我定义了一些兴趣点。其中一些有一个 Kontakt.io 信标的名称,它绑定(bind)到一个特定的 PoI(我的意思是通常贴在信标标签上的名称)。现在我想在附近发现信标,
我正在为警报提示创建一个 trigger.io 插件。尝试从警报提示返回数据。这是我的代码: // Prompt + (void)show_prompt:(ForgeTask*)task{
您好,我是 Apple iOS 的新手。我阅读并搜索了很多关于推送通知的文章,但我没有发现任何关于 APNS 从 io4 到 ios 6 的新更新的信息。任何人都可以向我提供 APNS 如何在 ios
UITabBar 的高度似乎在 iOS 7 和 8/9/10/11 之间发生了变化。我发布这个问题是为了让其他人轻松找到答案。 那么:在 iPhone 和 iPad 上的 iOS 8/9/10/11
我想我可以针对不同的 iOS 版本使用不同的 Storyboard。 由于 UI 的差异,我将创建下一个 Storyboard: Main_iPhone.storyboard Main_iPad.st
我正在写一些东西,我将使用设备的 iTunes 库中的一部分音轨来覆盖 2 个视频的组合,例如: AVMutableComposition* mixComposition = [[AVMutableC
我创建了一个简单的 iOS 程序,可以顺利编译并在 iPad 模拟器上运行良好。当我告诉 XCode 4 使用我连接的 iPad 设备时,无法编译相同的程序。问题似乎是当我尝试使用附加的 iPad 时
我是一名优秀的程序员,十分优秀!