I'm trying to implement HERE maps SDK gestures. I added delegate and didRecivePan function. The pan gesture is recognized by the application.
Problem is that my mapView gets frozen for the time my pan gesture is being used and it creates lagging like map scrolling which makes UX really annoying. Is there anyway this can be resolved.
var follow : Bool = true;
#IBOutlet weak var mapView: NMAMapView!
let positionManager = NMAPositioningManager.self
let gestureMarker = NMAMapMarker.self
override func viewDidLoad() {
super.viewDidLoad()
mapView.gestureDelegate = self
}
#objc func didLosePosition(){ print("Position lost")}
#objc func positionDidUpdate(){
/* Possible solution but an ugly one!
if(follow == true){
mapView.gestureDelegate = self
}else if(follow == false){
mapView.gestureDelegate = nil
}*/
print("Follow \(follow)")
print("position updated")
let position = positionManager.sharedInstance().currentPosition
print(position!.coordinates ?? "xx")
if(follow){
mapView.set(geoCenter:(position?.coordinates)!,animation: .linear)
}
}
override func viewWillAppear(_ animated: Bool) {
if(positionManager.sharedInstance().startPositioning()){
// Register to positioning manager notifications
NotificationCenter.default.addObserver(self, selector: #selector(self.positionDidUpdate), name: NSNotification.Name.NMAPositioningManagerDidUpdatePosition, object: positionManager.sharedInstance())
NotificationCenter.default.addObserver(self, selector: #selector(self.didLosePosition), name: NSNotification.Name.NMAPositioningManagerDidLosePosition, object: positionManager.sharedInstance())
}
super.viewWillAppear(animated)
mapView.zoomLevel = 17
mapView.set(geoCenter: NMAGeoCoordinates(latitude: 45.921189263505788, longitude: 14.234863696633125),
animation: .linear)
let mapMarker = NMAMapMarker(geoCoordinates: NMAGeoCoordinates(latitude: 45.921189263505788, longitude: 14.234863696633125), icon: NMAImage(uiImage: UIImage(named: "driver") ?? UIImage())!)
mapView.positionIndicator.isVisible = true;
mapView.positionIndicator.isAccuracyIndicatorVisible = true
mapView.positionIndicator.set(displayObject: mapMarker, toLayer: NMAMapLayerType.foreground)
mapView.copyrightLogoPosition = NMALayoutPosition.centerRight
}
func mapView(_ mapView: NMAMapView, didReceivePan translation: CGPoint, at location: CGPoint) {
follow = false
}
#IBAction func followButton(_ sender: UIButton) {
follow = true
mapView.set(geoCenter: (positionManager.sharedInstance().currentPosition?.coordinates)!,
animation: .linear)
mapView.zoomLevel = 17
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Related
So I have a MapView which adds a pin to the current user location. Also I have two textfields which displays the coordinates of the pin. I want to add two features:
When I drag and drop the pin, I want to update the coordinates inside the textfields.
When I edit the coordinates in the textfield, it should move the pin to the updated coorinates from the textfields.
Here my code where i handle everything for MapView. Im still pretty new to coding, so the code could be confusing, sorry for that.
class LocateVC: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var finishedLocateButton: UIButton!
#IBOutlet weak var relocateButton: UIButton!
var coordinates: [[Double]]!
var latTitleLabel:[String]!
var latValueLabel:[String]!
var lngTitleLabel:[String]!
var lngValueLabel: [String]!
var isEdited = false
var customCallout: UIView?
//Important to track location
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.backgroundColor = Colors.grey.cgColor
//button titles
relocateButton.isHidden = true
relocateButton.setTitle("Relocate", for: .normal)
finishedLocateButton.setTitle("Continue", for: .normal)
navigationItem.title = "Location"
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
self.mapView.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
if isEdited == false {
if CLLocationManager.locationServicesEnabled() {
addPin()
}
}
// Latitude,Longitude
coordinates = [
[ProductData.shared.latitude!, ProductData.shared.longitude!],
]
} //end of viewDidLoad
override func viewWillAppear(_ animated: Bool) {
super.dismiss(animated: animated)
}
public func removePin() {
}
func dropPinFor(placemark: MKPlacemark) {
for annotation in mapView.annotations {
if annotation.isKind(of: MKPointAnnotation.self) {
// mapView.removeAnnotation(annotation) // removing the pins from the map
}
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
mapView.addAnnotation(annotation)
}
//1
public func addPin() {
if isEdited == false {
ProductData.shared.latitude = locationManager.location?.coordinate.latitude
ProductData.shared.longitude = locationManager.location?.coordinate.longitude
}
self.mapView.delegate = self
// adds an annotation to coordinates from productData
let point = ProductAnnotation(coordinate: CLLocationCoordinate2D(latitude: ProductData.shared.latitude! , longitude: ProductData.shared.longitude!))
self.mapView.addAnnotation(point)
// 3
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: ProductData.shared.latitude!, longitude: ProductData.shared.longitude!), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
self.mapView.setRegion(region, animated: true)
}
public func editCoord() {
performSegue(withIdentifier: "editCoord", sender: CustomCalloutView.self)
}
#IBAction func relocateButtonClicked(_ sender: Any) {
addPin()
}
#IBAction func finishedLocateButtonClicked(_ sender: Any) {
performSegue(withIdentifier: "finishedLocateSegue2", sender: self)
}
//4
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
if view.isKind(of: CustomCalloutView.self ) || view.isKind(of: AnnotationView.self) || view.isKind(of: ProductAnnotation.self) {
return
} else {
customCallout?.removeFromSuperview()
}
}
//3
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if view.annotation is MKUserLocation {
return
}
//this creates the callout
let views = Bundle.main.loadNibNamed("CustomCalloutView", owner: nil, options: nil)
let calloutView = views?[0] as! CustomCalloutView
calloutView.delegate = self
calloutView.lngTitleLabel.text = "Lng"
calloutView.latTitleLabel.text = "Lat"
calloutView.lngTextField.text = String(format:"%f", ProductData.shared.longitude!)
calloutView.latTextField.text = String(format:"%f", ProductData.shared.latitude!)
calloutView.latTextField.layer.borderWidth = 0.0
calloutView.lngTextField.layer.borderWidth = 0.0
calloutView.latTextField.isEnabled = false
calloutView.lngTextField.isEnabled = false
calloutView.latTextField.keyboardType = .numberPad
calloutView.lngTextField.keyboardType = .numberPad
calloutView.alpha = 1.0
calloutView.layer.cornerRadius = 8
calloutView.center = CGPoint(x: view.bounds.size.width / 2, y: -calloutView.bounds.size.height*0.52)
customCallout = calloutView
view.addSubview(calloutView)
mapView.setCenter((view.annotation?.coordinate)!, animated: true)
}
//2
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
var annotationView = self.mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
if annotationView == nil{
annotationView = AnnotationView(annotation: annotation, reuseIdentifier: "Pin")
annotationView?.isDraggable = true
annotationView?.canShowCallout = false
} else {
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "dot")
return annotationView
}
func saveButtonTapped() {
customCallout?.removeFromSuperview()
}
}
extension LocateVC: CustomCalloutViewDelegate {
func didClickEditButton() {
editCoord()
}
func didClickSaveButton() {
saveButtonTapped()
}
}
and here my custom callout:
class CustomCalloutView: UIView {
#IBOutlet weak var latTitleLabel: UILabel!
#IBOutlet weak var lngTitleLabel: UILabel!
#IBOutlet weak var editButton: UIButton!
#IBOutlet weak var saveButton: UIButton!
#IBOutlet weak var latTextField: UITextField!
#IBOutlet weak var lngTextField: UITextField!
var delegate: CustomCalloutViewDelegate?
var isEditing: Bool = false
#IBAction func didClickEditButton(_ sender: Any) {
// delegate?.didClickEditButton()
isEditing = true
latTextField.layer.borderWidth = 1.0
lngTextField.layer.borderWidth = 1.0
latTextField.isEnabled = true
lngTextField.isEnabled = true
saveButton.isHidden = false
}
#IBAction func saveButtonTapped(_ sender: Any) {
if isEditing == true {
if let lat = latTextField.text {
ProductData.shared.latitude = Double(lat)
}
if let lng = lngTextField.text {
ProductData.shared.longitude = Double(lng)
}
latTextField.layer.borderWidth = 0.0
lngTextField.layer.borderWidth = 0.0
latTextField.isEnabled = false
lngTextField.isEnabled = false
self.saveButton.setTitle("Save", for: .normal)
isEditing = false
} else {
self.saveButton.setTitle("Cancel", for: .normal)
delegate?.didClickSaveButton()
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if self.bounds.contains(point) {
return true
} else {
return self.subviews.contains { $0.frame.contains(point) }
}
}
}
protocol CustomCalloutViewDelegate {
func didClickEditButton()
func didClickSaveButton()
}
any idea how i can achive that? Maybe in a general way.
Thank you in advance
Here's a link to DSCenterPinMapView. A CocoaPod I recommend you to use.
It is a custom MapView with an animated and customizable center pin useful for selecting locations in map.
Here is also a guide on installing CocoaPods if it is new to you.
You should install the Pod an then, as a solution to your questions:
Implement the delegate so that you can get the location where pin drops.
pinMapView.delegate = self
extension MyViewController: DSCenterPinMapViewDelegate {
func didStartDragging() {
// My custom actions
}
func didEndDragging() {
// My custom actions
selectedLocation = pinMapView.mapview.centerCoordinate
// Update Text field
}
}
After you finish adding the coordinates on your TextFields you should call
pinMapView.center(on coordinate: myTextFieldCoordinate)
to center the map on the desired location.
After changing the marker position get coordinates like
let latitude = marker.coordinate.latitude
let longitude = marker.coordinate.longitude
And to set coordinate
let coordinate = CLLocationCoordinate2D(latitude: "your lat", longitude: "your long")
marker.coordinate = coordinate
mapView.setCenter(coordinate, animated: true)
if you not keeping the marker reference then
guard let marker = self.appleMap.annotations.first else {return}
let latitude = marker.coordinate.latitude
let longitude = marker.coordinate.longitude
And to set coordinate
guard let marker = self.appleMap.annotations.first else {return}
let coordinate = CLLocationCoordinate2D(latitude: "your lat", longitude: "your long")
marker.coordinate = coordinate
mapView.setCenter(coordinate, animated: true)
Hope that will help.
I have a view that is with the black border and it has two different views on it. And these views are overlapping in a small area. And each of them has own UITapGestureRecognizer. When I tap each item's discrete area, the action of that item is triggered. But when I tap the common area, only the second view's action is triggered. I want that both actions have to be triggered. How can I achieve this? Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var outerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
outerView.layer.borderWidth = 2.0
outerView.layer.borderColor = UIColor.black.cgColor
view1.layer.borderWidth = 2.0
view1.layer.borderColor = UIColor.red.cgColor
view2.layer.borderWidth = 2.0
view2.layer.borderColor = UIColor.blue.cgColor
self.initialize()
}
private func initialize(){
let tapGesture1 = UITapGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = UITapGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
self.view1.addGestureRecognizer(tapGesture1)
self.view2.addGestureRecognizer(tapGesture2)
}
#objc func detectTap1(_ gesture : UITapGestureRecognizer) {
print("detectTap1")
}
#objc func detectTap2(_ gesture : UITapGestureRecognizer) {
print("detectTap2")
}
}
Kindly share your suggestions.
For this problem i have found this solution, maybe is not the best solution but it works, i will look for further improvements anyway
I had subclassed UIGestureRecognizer class
Updated
import UIKit
import UIKit.UIGestureRecognizerSubclass
class CustomGestureRecognizer: UIGestureRecognizer {
var anotherGestureRecognizer : CustomGestureRecognizer?
private var touchBeganSended : Bool = false
private var touchLocation : CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let validTouch = touches.first?.location(in: self.view) {
if (self.view!.point(inside: validTouch, with: event)) {
if(!touchBeganSended) {
touchBeganSended = true
touchLocation = validTouch
anotherGestureRecognizer?.touchesBegan(touches, with: event)
state = .recognized
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
if let validTouch = touches.first?.location(in: self.view) {
if (self.view!.point(inside: validTouch, with: event)) {
if(touchBeganSended) {
touchBeganSended = false
anotherGestureRecognizer?.touchesEnded(touches, with: event)
state = .recognized
}
}
}
}
override func location(in view: UIView?) -> CGPoint {
if let desiredView = view {
if(desiredView == self.view) {
return touchLocation ?? CGPoint(x: 0, y: 0)
} else {
return super.location(in: view)
}
} else {
return super.location(in: view)
}
}
}
Updated
then you need to modify your initialize() method to this one, with the last update you don't need to take into account which view is on top on view hierarchy
private func initialize(){
let tapGesture1 = CustomGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = CustomGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
tapGesture1.cancelsTouchesInView = true
tapGesture1.delegate = self
tapGesture2.cancelsTouchesInView = true
tapGesture2.delegate = self
self.view1.addGestureRecognizer(tapGesture1)
tapGesture1.anotherGestureRecognizer = tapGesture2
tapGesture2.anotherGestureRecognizer = tapGesture1
self.view2.addGestureRecognizer(tapGesture2)
}
this works as you can see here
Try the following:
private func initialize(){
let tapGesture1 = UITapGestureRecognizer(target: self, action: #selector(detectTap1(_:)))
let tapGesture2 = UITapGestureRecognizer(target: self, action: #selector(detectTap2(_:)))
tapGesture1.cancelsTouchesInView = false
tapGesture2.cancelsTouchesInView = false
self.view1.addGestureRecognizer(tapGesture1)
self.view2.addGestureRecognizer(tapGesture2)
}
When you set
gesture.cancelsTouchesInView = false
it propagates the gesture to the views underneath.
Try to implement this UIGestureRecognizerDelegate method:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
...
private func initialize() {
...
gesture1.delegate = self
gesture2.delegate = self
...
}
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
// its up to you
//guard otherGestureRecognizer == yourAnotherGesture else { return false }
return true
}
I try to detect a tap on an UIImageView while it is in the process of animation, but it does't work.
What I do (swift 4):
added UIImageView via StoryBoard:
#IBOutlet weak var myImageView: UIImageView!
doing animation:
override func viewWillAppear (_ animated: Bool) {
super.viewWillAppear (animated)
myImageView.center.y + = view.bounds.height
}
override func viewDidAppear (_ animated: Bool) {
super.viewDidAppear (animated)
UIView.animate (withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y - = self.view.bounds.height
})
}
try to detect the tap:
override func viewDidLoad () {
super.viewDidLoad ()
let gestureSwift2AndHigher = UITapGestureRecognizer (target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer (gestureSwift2AndHigher)
}
#objc func actionUITapGestureRecognizer () {
print ("actionUITapGestureRecognizer - works!")
}
Please, before voting for a question, make sure that there are no normally formulated answers to such questions, understandable to the beginner and written in swift above version 2, so I can not apply them for my case.
Studying this problem, I realized that it is necessary to also tweak the frame !? But this is still difficult for me. Tell me, please, what I need to add or change in the code below.
Thank you for your help.
class ExampleViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// action by tap
let gestureSwift2AndHigher = UITapGestureRecognizer(target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer(gestureSwift2AndHigher)
}
// action by tap
#objc func actionUITapGestureRecognizer (){
print("actionUITapGestureRecognizer - works!") // !!! IT IS DOES NOT WORK !!!
}
// hide UIImageView before appear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
// show UIImageView after appear with animation
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
To detect touch on a moving (animated) view, simply override hitTest using the presentation layer:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return (layer.presentation()!.frame)
.contains(self.convert(point, to: superview!)) ? self : nil
}
In the example at hand
It works with any and all gesture recognizers
DO NOT modify any frames, or anything else, at the view controller level
Simply subclass the view itself, adding the override above
Don't forget that naturally, if you want to stop the animation once the item is grabbed, do that (in your view controller) with yourPropertyAnimator?.stopAnimation(true) , yourPropertyAnimator = nil
You CANNOT do what you want using UITapGestureRecognizer because it uses frame based detection and detects if a touch was inside your view by checking against its frame..
The problem with that, is that animations already set the view's final frame before the animation even begins.. then it animates a snapshot of your view into position before showing your real view again..
Therefore, if you were to tap the final position of your animation, you'd see your tap gesture get hit even though your view doesn't seem like it's there yet.. You can see that in the following image:
https://i.imgur.com/Wl9WRfV.png
(Left-Side is view-hierarchy inspector)..(Right-Side is the simulator animating).
To solve the tapping issue, you can try some sketchy code (but works):
import UIKit
protocol AnimationTouchDelegate {
func onViewTapped(view: UIView)
}
protocol AniTouchable {
var animationTouchDelegate: AnimationTouchDelegate? {
get
set
}
}
extension UIView : AniTouchable {
private struct Internal {
static var key: String = "AniTouchable"
}
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
private func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
var animationTouchDelegate: AnimationTouchDelegate? {
get {
return objc_getAssociatedObject(self, &Internal.key) as? AnimationTouchDelegate
}
set {
objc_setAssociatedObject(self, &Internal.key, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self)
var didTouch: Bool = false
let views = self.getAllSubviews() as [UIView]
for view in views {
if view.layer.presentation()?.hitTest(touchLocation) != nil {
if let delegate = view.animationTouchDelegate {
didTouch = true
delegate.onViewTapped(view: view)
}
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
deinit {
self.myImageView.animationTouchDelegate = nil
}
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
self.myImageView.animationTouchDelegate = self
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
It works by overriding touchesBegan on the UIView and then checking to see if any of the touches landed inside that view.
A MUCH better approach would be to just do it in the UIViewController instead..
import UIKit
protocol AnimationTouchDelegate : class {
func onViewTapped(view: UIView)
}
extension UIView {
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self.view)
var didTouch: Bool = false
for view in self.view.getAllSubviews() {
if view.isUserInteractionEnabled && !view.isHidden && view.alpha > 0.0 && view.layer.presentation()?.hitTest(touchLocation) != nil {
didTouch = true
self.onViewTapped(view: view)
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}
Explanation
I'm trying to build a character selection menu similar to Crossy Road's one (as you can see here). So I found this iCarousel, which would help me with all of it, but everything I read talk about implementing it to a ViewController, which isn't my case. I'm using GameScene and I didn't found anything talking about it. Is there anyway I could implement it to my game? or even another effect similar to the character selection menu I mentioned above?
Attempt (beyowulf)
You can download it here.
GameScene.swift
import SpriteKit
class GameScene: SKScene {
var show = SKSpriteNode()
var hide = SKSpriteNode()
func showCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("showCharPicker", object: nil)
}
func hideCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("hideCharPicker", object: nil)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
print("didMoveToView")
show = SKSpriteNode(imageNamed: "show")
show.anchorPoint = CGPointZero
show.position = CGPointZero
addChild(show)
hide = SKSpriteNode(imageNamed: "hide")
hide.anchorPoint = CGPointZero
hide.position = CGPoint(x: self.frame.width / 2 - hide.frame.width / 2, y: 0)
addChild(hide)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == show{
print("show")
showCharPicker()
}
else if node == hide{
print("hide")
hideCharPicker()
}
}
}
}
GameViewController.swift
import UIKit
import SpriteKit
class GameViewController: UIViewController, iCarouselDataSource, iCarouselDelegate{
var squaresArray : NSMutableArray = NSMutableArray()
#IBOutlet weak var carousel: iCarousel!
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func showCarousel(){
self.carousel.hidden = false
}
func hideCarousel(){
self.carousel.hidden = true
}
override func viewDidLoad(){
super.viewDidLoad()
// Configure iCarousel
carousel.dataSource = self
carousel.delegate = self
carousel.type = .CoverFlow
carousel.reloadData()
self.carousel.hidden = true
// Register showCarousel and hideCarousel functions
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: "showCharPicker", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.hideCarousel), name: "hideCharPicker", object: nil)
// Configure view
let skView = SKView()
self.view.insertSubview(skView, belowSubview: self.carousel)
skView.frame = self.view.bounds
// Additionals
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
// Configure scene
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
scene.size = self.view.bounds.size
skView.presentScene(scene)
}
//iCarousel
override func awakeFromNib(){
super.awakeFromNib()
squaresArray = NSMutableArray(array: ["square1","square2","square3"])
}
func numberOfItemsInCarousel(carousel: iCarousel) -> Int{
return squaresArray.count
}
func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger){
//self.hideCarousel()
}
func carousel(carousel: iCarousel, viewForItemAtIndex index: Int, reusingView view: UIView?) -> UIView{
var itemView: UIImageView
if (view == nil){
itemView = UIImageView(frame:CGRect(x:0, y:0, width:200, height:200))
itemView.contentMode = .Center
}
else{
itemView = view as! UIImageView;
}
itemView.image = UIImage(named: "\(squaresArray.objectAtIndex(index))")
return itemView
}
func carousel(carousel: iCarousel, valueForOption option: iCarouselOption, withDefault value: CGFloat) -> CGFloat{
if (option == .Spacing){
return value * 2
}
return value
}
}
What's happening:
Thanks in advance,
Luiz.
You can use NSNotifications to show your character picker. You just need to observe the notifications posted by your SKScene. Your viewDidLoad should look something like:
override func viewDidLoad(){
super.viewDidLoad()
carousel.type = .CoverFlow
carousel.reloadData()
let spriteKitView = SKView()
spriteKitView.frame = self.view.bounds
self.view.insertSubview(spriteKitView, belowSubview: self.carousel)
spriteKitView.showsFPS = true
spriteKitView.showsNodeCount = true
spriteKitView.ignoresSiblingOrder = true
self.gameScene = GameScene(size:self.view.bounds.size)
self.gameScene.scaleMode = .AspectFill
self.gameScene.imageName = self.images[0] as! String
self.carousel.hidden = true
spriteKitView.presentScene(self.gameScene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: gameScene.kShowNotification, object: nil)
}
You'll want to implementing carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger) so you know what is selected, and so you can return to game play. For example:
func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger)
{
self.gameScene.imageName = self.images[index] as! String
self.hideCarousel()
}
You also need to remove observing before your view controller is deallocated.
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Your SKScene can then post a notifications:
import SpriteKit
class GameScene: SKScene {
var imageName = "square1"{
didSet{
self.hidden = false
self.childNode.texture = SKTexture(imageNamed: imageName)
}
}
let kShowNotification = "showPicker"
var childNode = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.childNode = SKSpriteNode(imageNamed: imageName)
self.childNode.anchorPoint = CGPointZero
self.childNode.position = CGPointZero
self.addChild(self.childNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.showCharPicker()
}
func showCharPicker()
{
self.hidden = true
NSNotificationCenter.defaultCenter().postNotificationName(kShowNotification, object: nil)
}
}
If you want to change hit detection, you need to subclass the view for which you need it to change. This case your iCarousel view.
You can then either override hitTest or pointInside. I've created an iCarousel subclass and overrode pointInside to only return true when the point is inside one of the carousel's contentView's subviews.
class CarouselSubclass: iCarousel {
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
var inside = false
for view in self.contentView.subviews
{
inside = CGRectContainsPoint(view.frame, point)
if inside
{
return inside
}
}
return inside
}
}
You need to remember to change the class of your carousel in interface builder and update your outlet as well.
I have 2 views which the childView communicates with the parentView. I am using Google maps SDK to track a users location however if I startUpdatingLocation() using a button, the protocol does not work however if I initiate startUpdatingLocation() as soon as the app loads it works fine. Below are my Two controller files.
I am simply trying to display the location in a label but not sure why the protocol is not working after button click.
Child:
import Foundation
import UIKit
protocol LocationUpdateDelegate{
func delegateUserLocation(mapChildController:MapChildController, userLocation: String)
}
class MapChildController: UIViewController, CLLocationManagerDelegate
{
#IBOutlet weak var mapView: GMSMapView!
let locationManager = CLLocationManager()
var didFindMyLocation = false
var myLocations:[String] = []
var alreadyUpdatedLocation = false
var locationDelegate:LocationUpdateDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var camera = GMSCameraPosition.cameraWithLatitude(-33.86, longitude: 151.20, zoom: 15, bearing: 0, viewingAngle: 45)
var mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.myLocationEnabled = true
self.view = mapView
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
println("iOS >= 8.0.0")
locationManager.requestWhenInUseAuthorization()
}
mapView.addObserver(self, forKeyPath: "myLocation", options: NSKeyValueObservingOptions.New, context: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if !didFindMyLocation {
let myLocation: CLLocation = change[NSKeyValueChangeNewKey] as! CLLocation
var mapView = self.view as! GMSMapView
mapView.camera = GMSCameraPosition.cameraWithTarget(myLocation.coordinate, zoom: 15.0)
mapView.settings.myLocationButton = true
didFindMyLocation = true
}
}
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
myLocations.append("\(locValue.latitude)/\(locValue.longitude)")
self.locationDelegate?.delegateUserLocation(self, userLocation: "From child")
println("Child location: \(locValue.latitude)/\(locValue.longitude)")
}
func trackingToggle()
{
if !alreadyUpdatedLocation
{
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
println("Tracking started")
alreadyUpdatedLocation = true
}
else
{
locationManager.stopUpdatingLocation()
println("Tracking stopped")
alreadyUpdatedLocation = false
}
}
}
Parent:
import UIKit
struct Constants {
static let embedSegue = "embedSegue"
}
class MapMainController: UIViewController, LocationUpdateDelegate
{
let mapChild = MapChildController()
var alreadyUpdatedLocation = false;
#IBOutlet weak var startTrackingButton: UIButton!
#IBOutlet weak var MapStatsView: UIView!
#IBOutlet weak var txtUserLocation: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let mapChildController = segue.destinationViewController as! MapChildController
mapChildController.locationDelegate = self
}
}
func delegateUserLocation(mapChildController: MapChildController, userLocation: String) {
println("Parent location: \(userLocation)");
txtUserLocation.text = userLocation
}
#IBAction func startTrackingTapped(sender: UIButton) {
mapChild.trackingToggle();
if !alreadyUpdatedLocation
{
alreadyUpdatedLocation = true
startTrackingButton.setTitle("Stop Tracking", forState: UIControlState.Normal)
}
else
{
alreadyUpdatedLocation = false
startTrackingButton.setTitle("Start Tracking", forState: UIControlState.Normal)
}
}
}
You are setting the mapChildController to self just before you segue to another view:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let mapChildController = segue.destinationViewController as! MapChildController
mapChildController.locationDelegate = self
After you just set the delegate to self your viewController went out of scope, now your delegate is nil again and will never be called.
You need to set the delegate in viewDidLoad