- 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/
我学习 SDL 二维编程已有一段时间了,现在我想创建一个结合使用 SDL 和 OpenGL 的程序。我是这样设置的: SDL_Init(SDL_INIT_VIDEO); window = SDL_Cr
尝试查找可在地块中使用的不同类型项目的列表 来自不同样本的投影类型: projection = list(type = "equirectangular") projection = list(typ
我正在尝试使用 Java Graphics API 绘制 GIF,但无法使用下面的代码成功绘制 GIF。仅绘制 GIF 的第一张图像或缩略图,但不播放。 public void paintCompon
我目前正在使用 JFrame 并尝试绘制一个矩形,但我不知道如何执行代码 paint(Graphics g),如何获取 Graphics 对象? package com.raggaer.frame;
这个领域的新手,希望得到一些帮助。 我有一个"Missile.java" 类,我在那里画东西。我想绘制一个 ImageView,我正在使用以下代码: ImageView v = (ImageView)
下面列出了圆形的例子 这是我的 JavaScript 代码。 最佳答案 假设您的 randomColor 是正确的,您只需要: 从 canvas.onclick 中移除 context.clearR
我在绘制和缩放 ImageView 时遇到问题。请帮帮我.. 当我画一些东西然后拖动或缩放图像时 - 绘图保留在原处,如您在屏幕截图中所见。而且我只需要简单地在图片上绘图,并且可以缩放和拖动这张图片。
我们可以在形式之外绘制图像和文本...我的意思是在字面上... 我知道问这个问题很愚蠢但是我们能不能... 最佳答案 您可以通过创建表单并将其 TransparentColor 属性设置为背景色来“作
我在绘制/布局期间收到 3 个对象分配警告 super.onDraw(canvas); canvas.drawColor(Color.WHITE); Paint textPaint = new Pai
我有一个示例时间序列数据框: df = pd.DataFrame({'year':'1990','1991','1992','1993','1994','1995','1996',
我试图想出一种简洁的方法来绘制 R 数据框中所有列的 GridView 。问题是我的数据框中既有离散值又有数值。为简单起见,我们可以使用 R 提供的名为 iris 的示例数据集。我会使用 par(mf
我有一个由 10 列和 50 行组成的 data.frame。我使用 apply 函数逐列计算密度函数。现在我想绘制我一次计算的密度。 换句话说,而不是绘图... plot(den[[1]]) plo
我想知道我们如何才能在第一个和第二个组件之外绘制个人,如下所示: 最佳答案 这可能有效: pc.cr <- princomp(USArrests, cor = TRUE) pairs(pc.cr$lo
我是Pandas和matplotlib的新手,想绘制此DataFrame season won team matches pct_won 0 20
我正在尝试为 distplot 子图编写一个 for 循环。 我有一个包含许多不同长度列的数据框。 (不包括 NaN 值) fig = make_subplots( rows=len(asse
我想创建一个具有密度的 3d 图。 我使用函数 density 首先为特定的 x 值创建一个二维图,然后该函数创建密度并将它们放入 y 变量中。现在我有第二组 x 值并将其再次放入密度函数中,然后我得
全部, 我一直在研究全局所有 MTB 步道的索引。我是 Python 人,所以对于所有涉及的步骤,我都尝试使用 Python 模块。 我能够像这样从 OSM 立交桥 API 中获取关系: from O
我正在使用 e1071 包中的支持向量机对我的数据进行分类,并希望可视化机器实际如何进行分类。但是,在使用 plot.svm 函数时,出现无法解决的错误。 脚本: library("e1071") d
我制作了以下图表,它是使用 xts 对象创建的。 我使用的代码很简单 plot(graphTS1$CCLL, type = "l", las = 2, ylab = "(c)\nCC for I
在绘制状态图时,您如何知道哪些状态放在框中,哪些状态用于转换箭头?我注意到转换也是状态。 我正在查看 this page 上的图 1 : 最佳答案 转换不是状态。转换是将对象从一种状态移动到下一种状态
我是一名优秀的程序员,十分优秀!