preface
In the previous article, we completed the user action logic for the jigsaw element splitting and basic drag and drop. Now let’s complete the logic when the user drags and drops puzzle elements.
In real life, jigsaw puzzles are always “locked” on a certain canvas, where players can only use their imagination to restore the puzzle. Therefore, we also need to define a “region” on the canvas for the user.
From the previous two articles, we know that the jigsaw elements in the “Li Jin Jigsaw” can only be manipulated on the left side of the canvas, not beyond the scope of the screen. So we need to make a constraint on the puzzle elements.
Limit the puzzle
In order to better see the boundary of the elements, we first add “boundaries” to the puzzle elements. Supplement Puzzle
extension Puzzle {
@objc
fileprivate func pan(_ panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began:
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 1
case .changed:
case .ended:
layer.borderWidth = 0
default: break
}
let translation = panGesture.translation(in: superview)
center = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
panGesture.setTranslation(.zero, in: superview)
}
}
Copy the code
The idea of adding boundaries is relatively simple. Our purpose is to allow users to have a better boundary control over puzzle elements during the process of dragging and dropping them. Run the project, drag and drop the puzzle element, the boundary of the puzzle element has been added!
! [Puzzle element boundary]](i.loli.net/2019/09/08/…)
Limiting the movable positions of Puzzle elements can be done in Puzzle’s drag-and-drop gesture callback method for boundary confirmation. Let’s start by “preventing” puzzle elements from crossing the middle line of the canvas.
extension Puzzle {
@objc
fileprivate func pan(_ panGesture: UIPanGestureRecognizer) {
let translation = panGesture.translation(in: superview)
let newRightPoint = centerX + width / 2
switch panGesture.state {
case .began:
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 1
case .changed:
if newRightPoint > superview!.width / 2 {
right = superview!.width / 2
}
case .ended:
layer.borderWidth = 0
default: break
}
center = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
panGesture.setTranslation(.zero, in: superview)
}
}
Copy the code
In the jigsaw element drag-and-drop callback method, in the.change judgment of the gesture state enumeration value, based on the “rightmost” position of the current jigsaw element, X + self.frame.size. Width / 2 against the middle position of the superview to determine whether the jigsaw element is out of bounds.
Run the project! We can no longer drag puzzle elements onto the canvas on the right
Maintaining state
After the explanation of “Can YOU turn off the lights” in the last game, we have roughly understood how to maintain the game logic through state. For a puzzle game, whether the puzzle elements can be restored in a certain order determines whether the game will win or not.
“Li Jin puzzles” it’s still a 2 d game, careful, you must also find the game and essentially “can pass a light” this game is the same, we can divide the game canvas according to certain rules of cutting out, and through a two-dimensional mapping list and cutting completed puzzle elements do, every time a user after the drag-and-drop behavior of jigsaw puzzle elements, To trigger a status update. Finally, we determine whether the player has won the current game based on the state after each update.
State to create
Our Puzzle class represents the Puzzle elements themselves, and the winning condition of a Puzzle game is that we put the Puzzle elements back together in a certain order, especially in a certain order. We can do this by attaching tags to puzzle objects to identify each piece of the puzzle.
class ViewController: UIViewController {
override func viewDidLoad(a) {
/ /...
for itemY in 0..<itemVCount {
for itemX in 0..<itemHCount {
let x = itemW * itemX
let y = itemW * itemY
let img = contentImageView.image!.image(with: CGRect(x: x, y: y, width: itemW, height: itemW))
let puzzle = Puzzle(size: CGSize(width: itemW, height: itemW), isCopy: false)
puzzle.image = img
/ / add the tag
puzzle.tag = (itemY * itemHCount) + itemX
print(puzzle.tag)
puzzles.append(puzzle)
view.addSubview(puzzle)
}
}
}
}
Copy the code
The viewController.swift file housed all the Puzzle instance objects that had been cut, and if we were maintaining the state of the game, required a contentPuzzles that managed the Puzzle elements that the user dragged onto the canvas. A match can only be won if the puzzle elements located on the canvas are placed on the canvas in a certain order.
To complete the logic expressed above, let’s start with the element diagram. Provide a “function bar” on the canvas that allows the user to drag and drop puzzle elements onto the canvas to complete the previously completed element drawing process.
The function bar
The function bar is used to host all the jigsaw pieces, adding relevant code in viewController.swift:
class ViewController: UIViewController {
// ...
let bottomView = UIView(frame: CGRect(x: 0, y: view.height, width: view.width, height: 64 + bottomSafeAreaHeight))
bottomView.backgroundColor = .white
view.addSubview(bottomView)
UIView.animate(withDuration: 0.25, delay: 0.5, options: .curveEaseIn, animations: {
bottomView.bottom = self.view.height
})
}
Copy the code
Run the project, the bottom function bar with animation, the effect is good ~
In order to better handle the functions carried in the bottom function bar, we need to wrap the bottom function bar and create a new class LiBottomView.
class LiBottomView: UIView {}Copy the code
Now we are going to put the puzzle elements in column “layout” to function, using UICollectionView long shop layout, also need to create a LiBottomCollectionView and LiBottomCollectionViewCell.
In the horizontal layout LiBottomCollectionView, we don’t have too many animation requirements, so it is relatively simple to implement.
class LiBottomCollectionView: UICollectionView {
let cellIdentifier = "PJLineCollectionViewCell"
var viewModels = [Puzzle] ()override init(frame: CGRect.collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
initView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func initView(a) {
backgroundColor = .clear
showsHorizontalScrollIndicator = false
isPagingEnabled = true
dataSource = self
register(LiBottomCollectionViewCell.self, forCellWithReuseIdentifier: "LiBottomCollectionViewCell")}}extension LiBottomCollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView.numberOfItemsInSection section: Int) -> Int {
return viewModels.count
}
func collectionView(_ collectionView: UICollectionView.cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LiBottomCollectionViewCell", for: indexPath) as! LiBottomCollectionViewCell
cell.viewModel = viewModels[indexPath.row]
return cell
}
}
Copy the code
In the new LiBottomCollectionViewCell added code.
class LiBottomCollectionViewCell: UICollectionViewCell {
var img = UIImageView(a)var viewModel: Puzzle? {
didSet { setViewModel() }
}
override init(frame: CGRect) {
super.init(frame: frame)
layer.borderWidth = 1
layer.borderColor = UIColor.darkGray.cgColor
layer.shadowColor = UIColor.black.cgColor
layer.shadowRadius = 10
layer.shadowOffset = CGSize.zero
layer.shadowOpacity = 1
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")}private func setViewModel(a) {
img.contentMode = .scaleAspectFit
img.image = viewModel?.image
img.frame = CGRect(x: 0, y: 0, width: width, height: height)
if !subviews.contains(img) {
addSubview(img)
}
}
}
Copy the code
Running the project, you can see that we have all the puzzle elements laid out in the function bar at the bottom
Function bar Figure above
When we drag a jigsaw element from the function bar onto the canvas, the jigsaw element originally located on the function bar needs to be removed. Let’s remove the jigsaw element from the function bar first.
The puzzle element data source on the function bar at the bottom comes from the viewModels of LiBottomCollectionView. When the data source is assigned, the reloadDate() method is called to refresh the page, so we only need to remove the puzzle element in the data source through a “method”.
Add the long-press gesture to the Cell in the function bar at the bottom. In the callback method of the long-press gesture recognizer, transfer the data source of the current Cell, delete the main data source through LiBottomCollectionView operation, and then execute the reloadData() method.
class LiBottomCollectionViewCell: UICollectionViewCell {
var longTapBegan: ((Int) - > ())?
var longTapChange: ((CGPoint) - > ())?
var longTapEnded: ((Int) - > ())?
var index: Int?
// ...
override init(frame: CGRect) {
// ...
let longTapGesture = UILongPressGestureRecognizer(target: self, action: .longTap)
addGestureRecognizer(longTapGesture)
}
// ...
}
extension LiBottomCollectionViewCell {
@objc
fileprivate func longTap(_ longTapGesture: UILongPressGestureRecognizer) {
guard let index = index else { return }
switch longTapGesture.state {
case .began:
longTapBegan?(index)
case .changed:
let translation = longTapGesture.location(in: superview)
let point = CGPoint(x: translation.x, y: translation.y)
longTapChange?(point)
case .ended:
longTapEnded?(index)
default: break}}}Copy the code
In the Cell’s long press gesture recognizer callback, we processed the gesture states.began,.changed, and.ended. In the. Began gesture state, pass the index of the current Cell to the superview through the longTapBegan() closure, and in the. Changed gesture state, The longTapChange() closure converts the coordinates of user operations on the current view to the same coordinates as the parent view, and the index of the current view is also passed in.ended methods.
In the superview LiBottomCollectionView, modify the cellForRow method:
// ...
extension LiBottomCollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView.cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LiBottomCollectionViewCell", for: indexPath) as! LiBottomCollectionViewCell
cell.viewModel = viewModels[indexPath.row]
cell.index = viewModels[indexPath.row].tag
cell.longTapBegan ={[weak self] index in
guard let self = self else { return }
guard self.viewModels.count ! = 0 else { return }
self.longTapBegan?(self.viewModels[index], cell.center)
}
cell.longTapChange = {
self.longTapChange?($0)
}
cell.longTapEnded = {
self.longTapEnded?(self.viewModels[$0])
self.viewModels.remove(at: $0)
self.reloadData()
}
return cell
}
}
Copy the code
In the cellForRow method, a view removal operation is performed on the cell’s closure, longTapEnded(), to remove the puzzle element from the bottom function bar when the user makes a gesture on the puzzle element in the bottom function bar and then “releases” the long-press gesture. The closure of the remaining two cells is passed to the corresponding superview through the collectionView.
Perform the image above operations on the puzzle elements at the LiBottomView level.
class LiBottomView: UIView {
// ...
private func initView(a) {
// ...
collectionView!.longTapBegan = {
let center = The $1
let tempPuzzle = Puzzle(size: $0.frame.size, isCopy: false)
tempPuzzle.image = $0.image
tempPuzzle.center = center
tempPuzzle.y + = self.top
self.tempPuzzle = tempPuzzle
self.superview!.addSubview(tempPuzzle)
}
collectionView!.longTapChange = {
guard let tempPuzzle = self.tempPuzzle else { return }
tempPuzzle.center = CGPoint(x: $0.x, y: $0.y + self.top)
}
}
}
Copy the code
Create a new Puzzle element in the longTapBegan() method. Note that you can’t just use the Puzzle object that was passed, or you’ll end up with some weird questions later because of references.
In longTapChange () method of maintenance triggered by in LiBottomCollectionViewCell long press to bring up a CGPoint gesture events. In the section above from the bottom bar, it’s easy to think of attaching a UIPanGesture drag gesture to a longTapBegan() new Puzzle object, but if you think about it, UILongPressGestureRecognizer is inherited from UIGestureRecognizer class, UIGestureRecognizer maintain a set of associated with the user gestures recognition process, whether it’s light sweep, drag and drop or long press, Essence is through at a certain time interval judgment user gestures of moving distance and trend which is to determine the specific gestures type, so we use UILongPressGestureRecognizer directly.
Run the project ~ select a puzzle you like in the bottom function bar, and long press it! Activate the gesture and slowly drag it onto the brush to feel it
Afterword.
In this article, we mainly focus on the logical realization of the bottom function bar, making the bottom function bar have the function of preliminary “function”, and improving the requirement of “element diagram” in the last article to make it more complete. At present, we have completed the following requirements:
- Jigsaw material preparation;
- Element above;
- State maintenance;
- Elemental adsorption;
- The UI perfect;
- Winning logic;
- Victory dynamic effect.
GitHub address: github.com/windstormey…