UIView Representative Does Not Conform to Protocol - swift

I'm new to coding and am working on an app to display graphs using XCODE (13.1)/Swift. I want to be able to swipe to go from graph to graph, but have been unable to get it functioning. I originally tried to use a class to call for the swipe, but ran into conflicts with the UIView.
I think that the answer is to use a UIViewRepresentative, but I got an error that it did not conform to protocol. Here is the code for the approach using class (I have commented the options out as I have been troubleshooting.
/*class SwipeView: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.view.isUserInteractionEnabled = true
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(tapForSwipe))
swipeRight.direction = .right
self.view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(tapForSwipe))
swipeLeft.direction = .left
self.view.addGestureRecognizer(swipeLeft)
}
#objc private func tapForSwipe(sender : UISwipeGestureRecognizer){
if sender.direction == .right {
Charttype = Charttype - 1
print("right")
}
else if sender.direction == .left {
Charttype = Charttype + 1
return
}
if Charttype > 16 {
Charttype = 16
return
}
if Charttype < 0 {
Charttype = 0
return
}
And here is the code for the UIViewRepresentative approach:
enter
struct swipeGesture : UIViewRepresentable {
func makeCoordinator() -> swipeGesture.Coordinator {
return swipeGesture.Coordinator()
}
func makeUIView(context: UIViewControllerRepresentableContext<swipeGesture>) -> UIView{
let view = UIView()
let left = UISwipeGestureRecognizer(target :context.coordinator, action: #selector(context.coordinator.left))
left.direction = .left
let right = UISwipeGestureRecognizer(target: self, action: #selector(context.coordinator.right))
right.direction = .right
view.addGestureRecognizer(left)
view.addGestureRecognizer(right)
return view
}
func updateUIView(_ uiView: UIView, context: UIViewControllerRepresentableContext<swipeGesture>) {
}
class Coordinator : NSObject{
#objc func left(){
print("left")
}
#objc func right(){
print("right")
}
}
I appreciate any help and guidance on this. Thanks!

You're confusing UIViewControllerRepresentable and UIViewRepresentable. Your two function calls above need to be:
func updateUIView(_ uiView: UIView, context: Self.Context) {
and
func makeUIView(context: Self.Context) -> UIView {
You're using UIViewControllerRepresentableContext<swipeGesture> which is not relevant here, and the source of your problems. The error message you're seeing is that swipeGesture (should be SwipeGesture, by the way, types start with upper case letters) doesn't conform to UIViewControllerRepresentable, which is true. The compiler only thinks it should because you've mentioned it in the signature of these two methods.
The wider issue may be why you think this is a solution to your problem. UIViewRepresentable is for injecting UIKit views into a SwiftUI app, yet from your question it seems like you're building a UIKit app anyway, so I'm not sure why you're going this route. But that's a different question, I think.

Related

Custom MapView Class Swift

I'm having some major issues and could use some help. I'm using some code from the company What3Words. They created a custom class:
open class MapView: MKMapView, MKMapViewDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
//all their code is here
}
My goal is to use a tap gesture in order to acquire a lat/long from the map. I can do this incredibly easily when using a MapViewKit drop in and I make the object conform to MKMapView!
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func tapped(_ sender: UITapGestureRecognizer) {
print("hey")
let tap = sender
let coord = tap.location(in: map)
print(coord)
print(tap.location(in: self))
print(tap.location(in: map))
map.convert(coord, to: map)
print(map.convert(coord, to: map))
}
}
But, if I conform it MKMapView! I can't access any of the specialty code within the open class.
and if I conform it to the open class I don't know how to use my tap gesture.
I'm supposed to call this code below in whatever VC I want the map to appear in:
super.viewDidLoad()
let map = MapViewController(frame: view.frame)
map.set(api: api)
map.set(center: "school.truck.lunch", latitudeSpan: 0.05, longitudeSpan: 0.05)
map.showsUserLocation = true
map.onError = { error in
self.showError(error: error)
}
self.view = map
}
// MARK: Show an Error
/// display an error using a UIAlertController, error messages conform to CustomStringConvertible
func showError(error: Error) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Error", message: String(describing: error), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
Any idea how I can get a tap gesture to work while still using this special class? I feel like I've tried everything. UGH!! Please assist.
The simplest way to do it is to add the following to your ViewController:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let location = touches.first?.location(in: map) {
map.screenCoordsToWords(point: location) { square in
print(square.words ?? "")
}
}
}
And in your MapView class add this:
func screenCoordsToWords(point: CGPoint, completion: #escaping (W3WSquare) -> () ) {
let coordinates = convert(point, toCoordinateFrom: self)
self.api?.convertTo3wa(coordinates: coordinates, language: "en") { square, error in
if let s = square {
completion(s)
}
}
}
But doing it this way can lead to confusion between MKMapView's touch event handler and your view controller. Depending on what you're trying to accomplish this might be okay, or not okay.
If it's an issue, you can instead attach a gesture recognizer inside your MapView, but there is a trick to prevent overriding the MKMapView's built in double tap recogniser:
func addGesture() {
/// when the user taps the map this is called and it gets the square info
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
tap.numberOfTapsRequired = 1
tap.numberOfTouchesRequired = 1
// A kind of tricky thing to make sure double tap doesn't trigger single tap in MKMapView
let doubleTap = UITapGestureRecognizer(target: self, action:nil)
doubleTap.numberOfTapsRequired = 2
addGestureRecognizer(doubleTap)
tap.require(toFail: doubleTap)
tap.delegate = self
addGestureRecognizer(tap)
}
#objc func tapped(_ gestureRecognizer : UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: self)
let coordinates = convert(location, toCoordinateFrom: self)
api?.convertTo3wa(coordinates: coordinates, language: "en") { square, error in
if let s = square {
print(s.words ?? "")
}
}
}
Then in your ViewController's viewDidLoad, set it up:
map.addGesture()

iOS SwiftUI: How to detect location of UITapGesture on view?

Using Mapbox, the map struct conforms to the UIViewRepresentable protocol. In my makeUIView() function, I create a tap gesture recognizer and add it to the map view.
struct Map: UIViewRepresentable {
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
func makeUIView(context: UIViewRepresentableContext<Map>) -> MGLMapView {
mapView.delegate = context.coordinator
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))
mapView.addGestureRecognizer(gestureRecognizer)
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<Map>) {
print("Just updated the mapView")
}
func makeCoordinator() -> Map.Coordinator {
Coordinator(appState: appState, self)
}
// other functions that make the struct have functions required by Mapbox
func makeCoordinator() -> Map.Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, MGLMapViewDelegate {
var control: Map
//a bunch of delegate functions
#objc func tappedMap(sender: UITapGestureRecognizer) {
let locationInMap = sender.location(in: control)
let coordinateSet = sender.convert(locationInMap, toCoordinateFrom: control)
}
}
}
Neither of the lines in the tappedMap function compile properly...also, when I have 'sender: UITapGestureRecognizer' in the parameters of tappedMap, I causes the application to crash when I tap the mapView--If I remove the parameter, then the function is at least called properly without crashing. Please help
OK, the first problem is your selector definition in the tapGesture declaration. Change it to this:
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tappedMap(sender:)))
Also, you need to manage the priority of gesture recognizers, since MGLMapView includes UIGestureRecognizer logic internally. I found a discussion of here:
Adding your own gesture recognizer to MGLMapView will block the
corresponding gesture recognizer built into MGLMapView. To avoid
conflicts, define which gesture takes precedence.
You can try this code in your project (pretty much just copied from the linked page above) EDIT: I needed to change the order of lines from my original answer:
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))
// HERE'S THE NEW STUFF:
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
gestureRecognizer.require(toFail: recognizer)
}
mapView.addGestureRecognizer(gestureRecognizer)
EDIT: I was able to test this.
I'm a little confused by the logic inside your tappedMap selector: maybe you meant something like this?
#objc func tappedMap(sender: UITapGestureRecognizer) {
let locationInMap = sender.location(in: control.mapView)
let coordinateSet = sender.view?.convert(locationInMap, to: control.mapView)
}

why is my gestureRecognizer returning nil?

I have tried to combine numerous tutorials to try and make a gesture recognizer start an animation on a tableView once a gesture is received on a mapView (shrinking table once map is moved like the old uber app). The animation functions, however once the animation is finished i am receiving "unexpectedly found nil when unwrapping an optional" - however it seems like it should not be nil, obviously it is, i just dont understand how. The error is 3/4 the way down, ive tried to reduce as much code as possible so parts are missing. My guess is that i have created 2 gesture recognizers which is causing the issue, but i am unsure how to combine them. Here is the code:
class RestaurantsVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {
var animator: UIViewPropertyAnimator?
var currentState: AnimationState!
var thumbnailFrame: CGRect!
var panGestureRecognizer: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(RestaurantsVC.handlePan(gestureRecognizer:)))
panGestureRecognizer.delegate = self
self.mapView.addGestureRecognizer(panGestureRecognizer)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#objc func handlePan (gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: self.view.superview)
switch gestureRecognizer.state {
case .began:
startPanning()
animator?.startAnimation()
case .ended:
let velocity = gestureRecognizer.velocity(in: self.view.superview)
endAnimation(translation: translation, velocity: velocity)
default:
print("Something went wrong handlePan")
}
}
func startPanning() {
var finalFrame:CGRect = CGRect()
switch currentState {
// code
}
animator = UIViewPropertyAnimator(duration: 1, dampingRatio: 0.8, animations: {
})
}
func endAnimation (translation:CGPoint, velocity:CGPoint) {
if let animator = self.animator {
self.panGestureRecognizer.isEnabled = false //(this line is where i get the error)//
switch self.currentState {
case .thumbnail:
animator.isReversed = false
animator.addCompletion({ _ in
self.currentState = .minimized
self.panGestureRecognizer.isEnabled = true
})
case .minimized:
animator.isReversed = true
animator.addCompletion({ _ in
self.currentState = .thumbnail
self.panGestureRecognizer.isEnabled = true
})
default:
print("unknown state")
}
}
}
}
It's nil because you're never actually assigning it.
In your viewDidLoad() function, you're actually redefining a local variable which shadows the instance variable:
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(RestaurantsVC.handlePan(gestureRecognizer:)))
^ The 'let' keyword is redefining this variable at the local scope
You should remove the let declaration, and instead assign this as:
self.panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(RestaurantsVC.handlePan(gestureRecognizer:)))
This could work still without storing a reference to the gesture recognizer. You could pass the values from the gesture recognizer passed into handlePan into startAnimation and endAnimation. This would reduce the need to have superfluous properties on your class

How to add a tap gesture to multiple UIViewControllers

I'd like to print a message when an user taps twice on the remote of the Apple TV. I got this to work inside a single UIViewController, but I would like to reuse my code so that this can work in multiple views.
The code 'works' because the app runs without any problems. But the message is never displayed in the console. I'm using Swift 3 with the latest Xcode 8.3.3. What could be the problem?
The code of a UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
_ = TapHandler(controller: self)
}
The code of the TapHandler class
class TapHandler {
private var view : UIView?
required init(controller : UIViewController) {
self.view = controller.view
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.message))
tapGesture.numberOfTapsRequired = 2
self.view!.addGestureRecognizer(tapGesture)
self.view!.isUserInteractionEnabled = true
}
#objc func message() {
print("Hey there!")
}
}
Your TapHandler just getting released. Try This:
var tapHandler:TapHandler? = nil
override func viewDidLoad() {
super.viewDidLoad()
tapHandler = TapHandler(controller: self)
}
I have tested the code and is working.

swift GestureRecognizer in cells

I would like to make long press tableview Cells, But i'm getting error:
UIGestureRecognizer.Type' does not have a member named 'state'
Here's the code
override func viewDidLoad() {
super.viewDidLoad()
var gesture: UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
gesture.minimumPressDuration = 2.0
self.view.addGestureRecognizer(gesture)
}
func longpressed() {
if(UIGestureRecognizer.state == UIGestureRecognizerState.Ended){
print("ended")
} else if (UIGestureRecognizer.state == UIGestureRecognizerState.Began){
print("began")
}
}
And Yes I've created Bridging-Header.h and imported this file:
#import <UIKit/UIGestureRecognizerSubclass.h>
I want swift tutorial not objective-c!
Add a gesture recogniser to your UITableView like
var gestureRec = UILongPressGestureRecognizer(target: self, action: "didTap:")
self.tableView.addGestureRecognizer(gestureRec)
Implement a didTap function, which would look something like this.
func didTap(sender : UIGestureRecognizer)
{
if sender.state == .Began {
var location = sender.locationInView(self.tableView)
var indexPath = self.tableView.indexPathForRowAtPoint(location)
var cell = self.tableView.cellForRowAtIndexPath(indexPath!)
}
}
This should work.
Try this in your longPress method:
func longpressed(sender: UILongPressGestureRecognizer) {
var state = sender.state
if(state.state == UIGestureRecognizerState.Ended){
print("ended")
} else if (state.state == UIGestureRecognizerState.Began){
print("began")
}
}