如何计算一个不接触UIView的随机CGPoint

时间:2017-08-09 14:25:18

标签: ios swift math uiview

这是一个示例视图:

enter image description here

我想计算一个CGPoint的框架,我可以在不触及任何现有卡片的情况下生成另一张卡片(UIView)。当然,它是可选的,因为视图可以充满卡片,因此没有免费的位置。

这就是我在屏幕上看到任何卡片以及我的功能现在的样子:

func freeSpotCalculator() -> CGPoint?{
    var takenSpots = [CGPoint]()
    for card in playableCards{
        takenSpots.append(card.center)
    }

}

我不知道从哪里开始以及如何在屏幕上计算随机CGPoint。随机帧与屏幕上的卡片具有相同的widthheight

7 个答案:

答案 0 :(得分:4)

这种天真的方法非常简单,但一旦屏幕填满就可能出现问题。生成随机CGPoint,其中x坐标介于0和屏幕宽度之间,y坐标介于0和屏幕高度之间。检查在该点处具有中心的矩形是否与任何现有视图相交。如果没有,你有随机的位置。

当问题出现时,屏幕开始填满。此时,您可能会在找到放置卡片的位置之前尝试许多随机点。您还可以达到不再适合的情况。你怎么知道你已经达到了?你的循环产生随机点会永远运行吗?

更智能的解决方案是跟踪屏幕上的可用空间。始终在这些自由空间内大致生成随机点。如果近似足够接近,您可以使用网格执行此操作。是否存在占用每个网格位置的卡?然后当最大的可用空间小于卡片矩形的大小时,你知道你已经完成了。这比天真的方法要多得多,但是当屏幕开始填满时它会更快,你肯定知道什么时候完成。

如果你知道你的屏幕空间总是超过卡可能占用的空间,那么天真的方法就可以了。

答案 1 :(得分:1)

想法

您知道容器UIView的宽度和高度。并且,每张卡具有相同的宽度和高度。我会通过计算网格来解决这个问题。

即使您想要随机显示卡片,依靠网格也会为您提供标准化的中心阵列,您可以使用这些中心生成随机性的外观(将卡片放置在作为网格一部分的任意随机中心,例如)。

如果您要在任何随机位置放置卡片,您可能只想使用CGRectIntersectsRect(card1.frame, card2.frame)来检测碰撞。

模式

首先,让我们将卡片的宽度和高度存储为常数。

let cardWidth = card.bounds.size.width
let cardHeight = card.bounds.size.height

作为概念的基本证明,我们假设您的容器视图宽度为250磅。我们假设卡片宽度为5分。这意味着您可以在一行中容纳250/5 = 50张卡片,其中一行具有一张卡片的高度。

连续中心的数量=该行中的卡数。每个中心的距离相同。在下图中(如果我甚至可以称之为),[]表示行的边缘。 -|-代表一张卡片,其中|是卡片的中心。

[ - | - - | - - | - - | - - | - ]

注意每个中心距离下一个中心是两个破折号。唯一的考虑因素是边缘旁边的中心距边缘一个点。就卡片而言,每个中心距离下一个中心是一整张卡片,边缘旁边的中心距离边缘半卡。

模式的关键

此模式表示特定行中任意卡片中心的x位置= (cardWidth / 2) + (the card index * cardWidth)。实际上,这个伪方程也适用于y位置。

代码

这里有一些使用这种方法创建一系列中心的Swift。

var centers = [CGPoint]()

let numberOfRows: CGFloat = containerView.bounds.size.height / cardHeight
let numberOfCardsPerRow: CGFloat = containerView.bounds.size.width / cardWidth

for row in 0 ..< Int(numberOfRows) {
    for card in 0 ..< Int(numberOfCardsPerRow) {

        // The row we are on affects the y values of all the centers
        let yBasedOnRow = (cardHeight / 2) + (CGFloat(row) * cardHeight)

        // The xBasedOnCard formula is effectively the same as the yBasedOnRow one
        let xBasedOnCard = (cardWidth / 2) + (CGFloat(card) * cardWidth)

        // Each possible center for this row gets appended to the centers array
        centers.append(CGPoint(x: xBasedOnCard, y: yBasedOnRow))

    }
}

此代码应为您的卡片创建一个中心网格。您可以围绕它构建一个函数,该函数返回一个随机中心,用于放置卡片并跟踪使用过的中心。

潜在的改进

首先,我认为centers数组可以构成一个矩阵([[CGPoint]]()),用于更多逻辑存储点。

其次,此代码目前假设容器视图的宽度和高度可以被卡宽度和高度整除。例如,容器宽度为177,卡宽度为5将导致一些问题。代码可以通过多种不同的方式来解决。

答案 2 :(得分:1)

最简单/性能最佳解决方案是在网格内随机显示卡片。诀窍是使网格大于卡片大小,因此网格内的卡片位置将是随机的。

轻松检查哪个位置占用,卡片将在“随机”帧上。

1-创建一个集合视图控制器,其中包含您想要显示的卡的总数(让我们说..在屏幕上输入的最大卡?)

2-将原型单元尺寸设置为大于卡。如果卡是50x80,则单元格应为70x110。

3-将UIImageView添加到具有约束的单元格中,这将是您的卡片图像

4-创建一个UICollectionViewCell,使用一种在单元格内随机设置卡片框架的方法(修改约束)

完成!

没有卡的单元格根据需要没有图像或空单元格。因此,要添加一张新卡,只需在空单元格之间随机添加,并在单元格中添加随机坐标卡。

您的UICollectionViewCell会喜欢这个

class CardCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var card: UIImageView!

override func awakeFromNib() {
    super.awakeFromNib()

    let newX = CGFloat(arc4random_uniform(UInt32(bounds.size.width-card.bounds.size.width+1)))
    let newY = CGFloat(arc4random_uniform(UInt32(bounds.size.height-card.bounds.size.height+1)))

    card.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: newX).active = true
    card.topAnchor.constraintEqualToAnchor(rightAnchor, constant: newY).active = true
   }
}

你的Collections View Controller应该是这样的 Collection View Image

答案 3 :(得分:0)

正如我在图片中看到的那样,所有卡片都在视图的底部对齐。因此,如果您从0 to origin of your cards row - one card height生成一个随机y位置,您只需根据视图的框架和卡片的大小获得CGPoint。

答案 4 :(得分:0)

如果您想在屏幕上随机放置卡片,可以执行以下操作:

 func random() -> CGFloat {
        return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
    }

    func random(min: CGFloat, max: CGFloat) -> CGFloat {
        return random() * (max - min) + min
    }

let actualX = random(min: *whatever amount*, max: *whatever amount*  )
let actualY = random(min: *Height of the first card*, max: *whatever amount*  )
 card.position = CGPoint(x: actualX, y: actualY )

然后将卡片随机放置在现有卡片上方。

答案 5 :(得分:0)

我不确定您是否打算有序地放置所有卡片。但如果你这样做,你可以这样做。

加载视图后,获取所有可能的卡位置,并将其与用作密钥的数字一起存储在地图中。然后,您可以生成一个从0到您存储在地图中的可能卡位置总数的随机数。然后,每次占据一个位置,清除地图上的值。

答案 6 :(得分:0)

您可以尝试使用CAShapeLayer和UIBezierPath。

  1. 为主视图创建一个CAShapeLayer,您将在其中添加子视图。我们将其称为主要形状图层。这将有助于检查估计的新视图是否在主视图中。
  2. 创建UIBezierPath实例。每当找到有效的新子视图时,将边添加到此路径。
  3. 在主视图中创建一个随机点。
  4. 基于随机点创建CGRect作为子视图的中心。我们称之为估计视图框架。
  5. 检查估计视图框架在主视图中是否完全可见。否则转到第3步。
  6. 使用路径对象检查估计视图框的4条边。如果边缘中的任何一个位于路径内,请转到步骤3.
  7. 如果4条边不在路径内,则估计的视图框是新视图的框架。
  8. 创建一个新的子视图并将其添加到主视图中。
  9. 将新视图的边缘添加到路径。
  10. 我使用上述逻辑在swift中创建了一个示例项目。

    https://github.com/mcabasheer/find-free-space-in-uiview

    您可以更改新视图的宽度和高度。我添加了一个条件,在尝试50次后停止寻找下一个可用空间。这有助于避免无限循环。

    正如@davecom强调的那样,使用随机数添加新视图会浪费空间,并且您将很快耗尽空间。如果您能够维护可用空间,则可以添加更多子视图。