GOOGLE ADS

sábado, 23 de abril de 2022

Volver a centrar el contenido de la vista de desplazamiento Swift

Estoy tratando de volver a centrar la vista de desplazamiento y mover el marco, cuando la ubicación actual del usuario sale del marco de la pantalla. Actualmente tengo un PDF y muestro la ubicación actual del usuario, estoy calculando el marco y la escala de zoom de la vista de desplazamiento para mostrar la ubicación actual en la Vista de PDF. Ya logro esta funcionalidad. Funciona perfectamente y ya estoy dibujando la ruta con la misma lógica cuando el usuario se mueve, pero estoy atascado en el último punto cuando el usuario se mueve y sale de la pantalla significa ocultarse de la pantalla del móvil, entonces necesitamos volver a centrar el ubicación actual.

Primer código: -

Código:-

override func viewDidLoad() {
super.viewDidLoad()
self.initPdfView()
self.initCurrentLocation()
}
func initPdfView() {
do {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
if let path = paths.first {
let fileURL = URL(fileURLWithPath: path).appendingPathComponent("MapBox")
let document = try PDFDocument.init(at: fileURL)
pdfController?.page = try document.page(0)
pdfController?.scrollDelegates = self
pdfController?.scrollView.layoutSubviews()
}
} catch {
print(error.localizedDescription)
}
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect.init(x: sender.contentOffset.x, y: sender.contentOffset.y, width: sender.contentSize.width*sender.zoomScale, height: sender.contentSize.height*sender.zoomScale)
self.visibleScrollViewRect = visibleRect
self.zooomLevel = sender.zoomScale
}
func initCurrentLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()

if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
self.currentLocation = locValue
}

Captura de pantalla:-

En la primera captura de pantalla, muestro la ubicación actual del usuario.

Primera captura de pantalla

En la segunda Captura de pantalla, estoy dibujando la ruta con la ayuda de la ubicación actual del usuario.

Segunda captura de pantalla

En la tercera captura de pantalla, cuando el usuario se mueve y sale de la pantalla, significa ocultarse de la pantalla del móvil.

Tercera captura de pantalla

Segundo código: -

func initMapDataUserView() {
guard let mapInfoJson = decodeMapInfo(with: "MapBoxUrl") else {
return
}

let position = CGPoint.init(x: mapInfoJson.rasterXYsize.first!, y: mapInfoJson.rasterXYsize.last!)
let pointerVal: UnsafePointer<Int8>? = NSString(string: mapInfoJson.projection).utf8String
let decoder = GeoDecode()
decoder.fetchPdfCoordinateBounds(with: position, projection: pointerVal, initialTransform: mapInfoJson.geotransform) { coordinate, error in
if let error = error {
debugPrint(error)
} else {
guard let coordinate = coordinate else {
return
}
self.coordinatesUserCurrentLocation = coordinate
self.initCurrentLocation()
}
}
}
func initPdfView() {
do {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
if let path = paths.first {
let fileURL = URL(fileURLWithPath: path).appendingPathComponent("MapBoxUrl")
let document = try PDFDocument.init(at: fileURL)
viewPDFController?.page = try document.page(0)
viewPDFController?.scrollDelegates = self
viewPDFController?.scrollView.layoutSubviews()
}
} catch {
print(error.localizedDescription)
}
}
func decodeMapInfo(with value: String) -> MapInfoJson? {
do {
guard let valueData = value.data(using:.utf8) else {
return nil
}
let decodedResult = try JSONDecoder().decode(MapInfoJson.self, from: valueData)
return decodedResult
} catch {
print("error: ", error)
}
return nil
}
extension MapPreviewViewController: scrollViewActions {
func scrollViewScroll(_ sender: UIScrollView) {
let visibleRect = CGRect.init(x: sender.contentOffset.x, y: sender.contentOffset.y, width: sender.contentSize.width*sender.zoomScale, height: sender.contentSize.height*sender.zoomScale)
self.visibleScrollViewRectUserScreen = visibleRect
self.zooomLevelScrollView = sender.zoomScale
if coordinatesUserCurrentLocation!= nil {
updateMarkerVisiblityOnPdfView()
}
}
}
extension MapPreviewViewController: CLLocationManagerDelegate {
func initCurrentLocation() {
locationManagerUserTest.delegate = self
locationManagerUserTest.desiredAccuracy = kCLLocationAccuracyBest
locationManagerUserTest.requestAlwaysAuthorization()

if CLLocationManager.locationServicesEnabled() {
locationManagerUserTest.desiredAccuracy = kCLLocationAccuracyBest
locationManagerUserTest.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
self.currentLocationUser = locValue
updateMarkerVisiblityOnPdfView()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {

}
func updateMarkerVisiblityOnPdfView() {
guard let locValue: CLLocationCoordinate2D = self.currentLocationUser else { return }
guard let coordinates = coordinatesUserCurrentLocation else { return }

let yFactor = (locValue.longitude - coordinates.minY) / (coordinates.maxY - coordinates.minY)
let xFactor = (coordinates.maxX - locValue.latitude) / (coordinates.maxX - coordinates.minX)

var positionX: Double = 0.0
var positionY: Double = 0.0

positionX = (yFactor*Double(visibleScrollViewRectUserScreen!.size.width))/Double(self.zooomLevelScrollView!)
positionY = (xFactor*Double(visibleScrollViewRectUserScreen!.size.height))/Double(self.zooomLevelScrollView!)

if visibleScrollViewRectUserScreen!.size.width < 1.0 {
positionX = (yFactor*Double(18))*Double(self.zooomLevelScrollView!)
positionY = (xFactor*Double(18))*Double(self.zooomLevelScrollView!)
}

var indexOfExistingImageView: Int?

for index in 0..<viewPDFController!.scrollView.subviews.count {
if let imageview = viewPDFController!.scrollView.subviews[index] as? UIImageView {
if imageview.image == currentmarkerImagView.image {
indexOfExistingImageView = index
}
}
}

self.currentmarkerImagView.center =.init(x: positionX, y: positionY)
self.viewPDFController!.scrollView.addSubview(currentmarkerImagView)
self.viewPDFController!.scrollView.bringSubviewToFront(currentmarkerImagView)

}
}
public protocol scrollViewActions {
func scrollViewScroll(_ sender: UIScrollView)
}
public class PdfViewViewController: UIViewController {
public var scrollView: UIScrollView!
public var overlayView: UIView!
public var contentView: UIView!
public var scrollDelegates: scrollViewActions?
public override func viewDidLoad() {
super.viewDidLoad()

scrollView.delegate = self
scrollView.contentInsetAdjustmentBehavior =.never
}
}
extension PdfViewViewController: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollDelegates?.scrollViewScroll(scrollView)
}
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
scrollDelegates?.scrollViewScroll(scrollView)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
scrollDelegates?.scrollViewScroll(scrollView)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
}
public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
}
}

Captura de pantalla actualizada:

En la primera captura de pantalla, muestro la ubicación actual del usuario.

Primera captura de pantalla

En la segunda captura de pantalla, cuando el usuario se mueve y sale de la pantalla, significa ocultarse de la pantalla del móvil.

Segunda captura de pantalla

Pregunta: ¿Puede alguien explicarme cómo volver a centrar la ubicación actual o mover el marco de la vista de desplazamiento cuando el usuario se mueve y sale de la pantalla?

Cualquier ayuda sería muy apreciada.

Gracias por adelantado.


Solución del problema

Bueno, obviamente, no podemos crear un nuevo proyecto, pegar el código que publicaste y ejecutarlo.

Así que... espero que esto ayude.

Una vista de desplazamiento .boundses el rectángulo visible de su .contentSize.

Entonces, si tomamos este ejemplo:


  • cree un "mapView" de 400x600 (como el viewForZooming)

  • agregue una subvista de "marcador" de 30x30 enorigin: x: 240, y: 400

  • use una vista de desplazamiento (fondo amarillo) con un marco de 200 x 300

  • restringir los 4 lados de la vista de mapa a la vista de desplazamiento.contentLayoutGuide


Se verá así al principio con un zoom de 1.0 (todo lo que esté fuera del marco de la vista de desplazamiento estará oculto, por supuesto):

ingrese la descripción de la imagen aquí

la vista de desplazamiento tendrá:

ContentSize: (400.0, 600.0)
Bounds: (0.0, 0.0, 200.0, 300.0)

Si nos desplazamos hasta la esquina inferior derecha, se verá así:

ingrese la descripción de la imagen aquí

con:

ContentSize: (400.0, 600.0)
Bounds: (200.0, 300.0, 200.0, 300.0)

Si nos acercamos a la escala de zoom 2.0, obtenemos:

ContentSize: (800.0, 1200.0)
Bounds: (0.0, 0.0, 200.0, 300.0)
ContentSize: (800.0, 1200.0)
Bounds: (600.0, 900.0, 200.0, 300.0)

Si nos acercamos a la escala de zoom 3.0, obtenemos:

ContentSize: (1200.0, 1800.0)
Bounds: (0.0, 0.0, 200.0, 300.0)
ContentSize: (1200.0, 1800.0)
Bounds: (1000.0, 1500.0, 200.0, 300.0)

y si hacemos zoom out a escala de zoom 0.5:

ContentSize: (200.0, 300.0)
Bounds: (0.0, 0.0, 200.0, 300.0)

La primera tarea es averiguar si el "marcador" está visible... si lo está, no necesitamos hacer nada. Si no está visible (desplazado fuera del marco), queremos centrarlo.

Entonces, si nos hemos desplazado así:

ingrese la descripción de la imagen aquí

podemos decir:

let r = marker.frame
let isInside = scrollView.bounds.contains(r)

en este caso, isInsideserá true.

Pero, si el marcador está fuera del marco de esta manera:

ingrese la descripción de la imagen aquí

queremos definir uno CGRectque tenga el mismo ancho y alto que los límites de la vista de desplazamiento, centrado en el centro del marcador:

ingrese la descripción de la imagen aquí

y podemos llamar:

scrollView.scrollRectToVisible(r, animated: true)

Por supuesto, si nuestra vista de marcador está cerca de un borde, así:

ingrese la descripción de la imagen aquí

eso es lo más cerca del centro que va a estar.

Aunque eso no es suficiente...

El marco de la vista de marcador siempre será su propio marco: se escala cuando cambia la escala de zoom de la vista de desplazamiento. Entonces, debemos tener en cuenta eso:

let r = marker.frame.applying(CGAffineTransform(scaleX: self.scrollView.zoomScale, y: self.scrollView.zoomScale))
let isInside = scrollView.bounds.contains(r)

Aquí hay una demostración de ejemplo completa... creamos la "vista de mapa" al doble del tamaño de la vista de desplazamiento, minZoom: 0.5, maxZoom: 3.0, organizamos un patrón de "marcadores" y usamos dos botones para "Resaltar y centrar si es necesario ":

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Nota: solo código de muestra; no está destinado a estar "listo para la producción":

class CenterInScrollVC: UIViewController, UIScrollViewDelegate {

let scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()

// can be any type of view
// using a "Dashed Outline" so we can see its edges
let mapView: DashView = {
let v = DashView()
v.backgroundColor = UIColor(white: 0.9, alpha: 0.5)
v.color =.blue
v.style =.border
return v
}()
var mapMarkers: [UIView] = []
var markerIndex: Int = 0

// let's make the markers 40x40
let markerSize: CGFloat = 40.0

// percentage of one-half of marker that must be visible to NOT screll to center
// 1.0 == entire marker must be visible
// 0.5 == up to 1/4 of marker may be out of view
// <= 0.0 == only check that the Center of the marker is in view
// can be set to > 1.0 to require entire marker Plus some "padding"
let pctVisible: CGFloat = 1.0

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor =.systemBackground

// a button to center the current marker (if needed)
let btnA: UIButton = {
let v = UIButton()
v.backgroundColor =.systemRed
v.setTitleColor(.white, for:.normal)
v.setTitleColor(.lightGray, for:.highlighted)
v.setTitle("Center Current if Needed", for: [])
v.addTarget(self, action: #selector(btnATap(_:)), for:.touchUpInside)
return v
}()

// a button to select the next marker, center if needed
let btnB: UIButton = {
let v = UIButton()
v.backgroundColor =.systemRed
v.setTitleColor(.white, for:.normal)
v.setTitleColor(.lightGray, for:.highlighted)
v.setTitle("Go To Marker - 2", for: [])
v.addTarget(self, action: #selector(btnBTap(_:)), for:.touchUpInside)
return v
}()

// add a view with a "+" marker to show the center of the scroll view
let centerView: DashView = {
let v = DashView()
v.backgroundColor =.clear
v.color = UIColor(red: 0.95, green: 0.2, blue: 1.0, alpha: 0.5)
v.style =.centerMarker
v.isUserInteractionEnabled = false
return v
}()

[btnA, btnB, mapView, scrollView, centerView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}

[btnA, btnB, scrollView, centerView].forEach { v in
view.addSubview(v)
}
scrollView.addSubview(mapView)

let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide

NSLayoutConstraint.activate([

// buttons at the top
btnA.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
btnA.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.7),
btnA.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),

btnB.topAnchor.constraint(equalTo: btnA.bottomAnchor, constant: 20.0),
btnB.widthAnchor.constraint(equalTo: btnA.widthAnchor),
btnB.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),

// let's inset the scroll view to make it easier to distinguish
scrollView.topAnchor.constraint(equalTo: btnB.bottomAnchor, constant: 40.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -40.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -40.0),

// overlay "center lines" view
centerView.topAnchor.constraint(equalTo: scrollView.topAnchor),
centerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
centerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
centerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// mapView Top/Leading/Trailing/Bottom to scroll view's CONTENT GUIDE
mapView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
mapView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
mapView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
mapView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),

// let's make the mapView twice as wide and tall as the scroll view
mapView.widthAnchor.constraint(equalTo: frameG.widthAnchor, multiplier: 2.0),
mapView.heightAnchor.constraint(equalTo: frameG.heightAnchor, multiplier: 2.0),

])

// some example locations for the Markers
let pcts: [[CGFloat]] = [

[0.50, 0.50],

[0.25, 0.50],
[0.50, 0.25],
[0.75, 0.50],
[0.50, 0.75],
[0.10, 0.15],
[0.90, 0.15],
[0.90, 0.85],
[0.10, 0.85],

]
for (i, p) in pcts.enumerated() {
let v = UILabel()
v.text = "\(i + 1)"
v.textAlignment =.center
v.textColor =.yellow
v.backgroundColor =.systemBlue
v.font =.systemFont(ofSize: 15.0, weight:.bold)
v.translatesAutoresizingMaskIntoConstraints = false
mapMarkers.append(v)
mapView.addSubview(v)
v.widthAnchor.constraint(equalToConstant: markerSize).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
NSLayoutConstraint(item: v, attribute:.centerX, relatedBy:.equal, toItem: mapView, attribute:.trailing, multiplier: p[0], constant: 0.0).isActive = true
NSLayoutConstraint(item: v, attribute:.centerY, relatedBy:.equal, toItem: mapView, attribute:.bottom, multiplier: p[1], constant: 0.0).isActive = true
}

scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 3.0
scrollView.delegate = self

}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// let's start with the scroll view zoomed out
scrollView.zoomScale = scrollView.minimumZoomScale

// highlight and center (if needed) the 1st marker
markerIndex = 0
let marker = mapMarkers[markerIndex % mapMarkers.count]
highlightMarkerAndCenterIfNeeded(marker, animated: true)
}
@objc func btnATap(_ sender: Any?) {
// to easily test "center if not visible" without changing the "current marker"
let marker = mapMarkers[markerIndex % mapMarkers.count]
highlightMarkerAndCenterIfNeeded(marker, animated: true)
}

@objc func btnBTap(_ sender: Any?) {
// increment index to the next marker
markerIndex += 1
let marker = mapMarkers[markerIndex % mapMarkers.count]
// center if needed
highlightMarkerAndCenterIfNeeded(marker, animated: true)
// update button title
if let b = sender as? UIButton, let m = mapMarkers[(markerIndex + 1) % mapMarkers.count] as? UILabel, let t = m.text {
b.setTitle("Go To Marker - \(t)", for: [])
}
}

func highlightMarkerAndCenterIfNeeded(_ marker: UIView, animated: Bool) {

// "un-highlight" all markers
mapMarkers.forEach { v in
v.backgroundColor =.systemBlue
}
// "highlight" the new marker
marker.backgroundColor =.systemGreen
// get the marker frame, scaled by zoom scale
var r = marker.frame.applying(CGAffineTransform(scaleX: self.scrollView.zoomScale, y: self.scrollView.zoomScale))

// inset the rect if we allow less-than-full marker visible
if pctVisible > 0.0 {
let iw: CGFloat = (1.0 - pctVisible) * r.width * 0.5
let ih: CGFloat = (1.0 - pctVisible) * r.height * 0.5
r = r.insetBy(dx: iw, dy: ih)
}

var isInside: Bool = true

if pctVisible <= 0.0 {
// check center point only
isInside = self.scrollView.bounds.contains(CGPoint(x: r.midX, y: r.midY))
} else {
// check the rect
isInside = self.scrollView.bounds.contains(r)
}

// if the marker rect (or point) IS inside the scroll view
// we don't do anything

// if it's NOT inside the scroll view
// center it

if!isInside {
// create a rect using scroll view's bounds centered on marker's center
let w: CGFloat = self.scrollView.bounds.width
let h: CGFloat = self.scrollView.bounds.height
r = CGRect(x: r.midX, y: r.midY, width: w, height: h).offsetBy(dx: -w * 0.5, dy: -h * 0.5)
if animated {
// let's slow down the animation a little
UIView.animate(withDuration: 0.75, delay: 0.0, options: [.curveEaseInOut], animations: {
self.scrollView.scrollRectToVisible(r, animated: false)
}, completion: nil)
} else {
self.scrollView.scrollRectToVisible(r, animated: false)
}
}

}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return mapView
}

}

No hay comentarios.:

Publicar un comentario

Flutter: error de rango al acceder a la respuesta JSON

Estoy accediendo a una respuesta JSON con la siguiente estructura. { "fullName": "FirstName LastName", "listings...