swift - Child View Controller para girar mientras Parent View Controller no lo hace
xcode uiviewcontroller (3)
Desde la biblioteca de desarrolladores de iOS, Preguntas y respuestas técnicas
La autorrotación se implementa aplicando una transformación de rotación a la ventana de la aplicación cuando el sistema determina que la interfaz debe rotar. Luego, la ventana ajusta sus límites para la nueva orientación y propaga este cambio hacia abajo a través de la jerarquía del controlador de vista a través de llamadas a cada controlador de vista y controlador de presentación -viewWillTransitionToSize: withTransitionCoordinator: método. A las invocaciones de este método se les proporciona un objeto coordinador de transición que contiene el delta de la transformación actual de la ventana y la nueva transformación, que se puede recuperar enviando un mensaje -targetTransform al coordinador de transición. Su controlador de vista o controlador de presentación puede derivar la transformación inversa adecuada y aplicarlo a la vista de destino. Esto anula el efecto de la transformación de la ventana en esa vista particular y da la apariencia de que la vista ha permanecido fija en su orientación actual a medida que el resto de la interfaz gira normalmente.
es decir, si tiene una vista llamada noRotatingView
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
noRotatingView.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (UIViewControllerTransitionCoordinatorContext) -> Void in
let deltaTransform = coordinator.targetTransform()
let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))
var currentRotation = self.noRotatingView.layer.valueForKeyPath("transform.rotation.z")?.floatValue
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
currentRotation = currentRotation! + (-1 * Float(deltaAngle)) + 0.0001
self.noRotatingView.layer.setValue(currentRotation, forKeyPath:"transform.rotation.z")
}) { (UIViewControllerTransitionCoordinatorContext) -> Void in
var currentTransform = self.noRotatingView.transform;
currentTransform.a = round(currentTransform.a);
currentTransform.b = round(currentTransform.b);
currentTransform.c = round(currentTransform.c);
currentTransform.d = round(currentTransform.d);
self.noRotatingView.transform = currentTransform;
}
}
He intentado hacer un proyecto de demostración en https://github.com/rishi420/TestCameraRotation
Lo que estoy tratando de hacer:
La vista principal gestionada por Parent View Controller NO DEBE ROTAR .
La vista secundaria gestionada por Child View Controller DEBE GIRAR en todas las orientaciones.
Lo que he intentado:
ParentViewController
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Portrait
}
override func shouldAutorotate() -> Bool {
return true
}
fun addChildViewController() {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
self.childViewController = storyBoard("Child View Controller") as? ChildViewController
self .addChildViewController(self.childViewController!)
self.childViewController! .didMoveToParentViewController(self)
self.view .addSubview(self.childViewController!.view)
self.childViewController!.view.frame = CGRectMake (40, 200, 400, 250)
}
ChildViewController
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .All
}
override func shouldAutorotate() -> Bool {
return true
}
Las orientaciones admitidas en la Información de implementación de Xcode se establecen en las cuatro.
Lo que consigo:
Ninguno de los giros de la vista. Si configuro la rotación de los padres a todos, todas las vistas giran juntas. Así que es todo o nada.
ACTUALIZAR
Cuando trato de colocar un observador para UIDeviceOrientationDidChangeNotification y uso UIDevice.currentDevice (). Orientación para rotar childView utilizando CGAffaineTransformMakeRotate, obtengo los resultados deseados. SIN EMBARGO, es decir, si giro hacia el paisaje y trato de desplegar el centro de notificaciones (o si recibo una notificación del sistema), que aún se encuentra en la orientación vertical (porque la aplicación todavía se encuentra en Vertical de forma nativa), childView girado gira volver al retrato para respetar la barra de estado / centro de notificación / notificación.
Stock iOS Camera App es el mejor ejemplo que puedo presentar. El lienzo principal no gira en diferentes orientaciones, sin embargo, la barra de estado, detrás de las escenas, rota respetando la rotación del dispositivo. También las subvistas giran dentro de ellas mismas para respetar diferentes orientaciones. Estoy tratando de lograr este comportamiento ...
Después de muchas idas y venidas con Apple ellos mismos, y que apuntaban al mismo enlace. developer.apple.com/library/ios/qa/qa1890/_index.html , tuve que hacer esto de manera fluida:
MotherViewController
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext) -> Void in
let deltaTransform: CGAffineTransform = coordinator.targetTransform()
let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)
var currentRotation: CGFloat = self.mainView.layer.valueForKeyPath("transform.rotation.z") as! CGFloat
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
currentRotation += -1 * deltaAngle + 0.0001
self.mainView.layer.setValue(currentRotation, forKeyPath: "transform.rotation.z")
}, completion: {(
context: UIViewControllerTransitionCoordinatorContext) -> Void in
// Integralize the transform to undo the extra 0.0001 added to the rotation angle.
var currentTransform: CGAffineTransform = self.mainView.transform
currentTransform.a = round(currentTransform.a)
currentTransform.b = round(currentTransform.b)
currentTransform.c = round(currentTransform.c)
currentTransform.d = round(currentTransform.d)
self.mainView.transform = currentTransform
})
}
MainViewController
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name:UIDeviceOrientationDidChangeNotification, object: nil)
func orientationChanged ( notification: NSNotification){
switch UIDevice.currentDevice().orientation {
case .Portrait:
self.rotateChildViewsForOrientation(0)
case .PortraitUpsideDown:
self.rotateChildViewsForOrientation(0)
case .LandscapeLeft:
self.rotateChildViewsForOrientation(90)
case .LandscapeRight:
self.rotateChildViewsForOrientation(-90)
default:
print ("Default")
}
}
Esto parece girar la vista del niño mientras mantiene la vista principal estática. Además, la aplicación parece saber la orientación correcta cuando llegan las notificaciones (porque la vista de la madre realmente gira detrás de la escena).
Un agradecimiento especial a jamesk y Warif Akhand Rishi por toda la ayuda!
El efecto logrado en la aplicación Cámara es similar al uso de una combinación de:
- la técnica descrita en developer.apple.com/library/ios/qa/qa1890/_index.html para evitar que una vista gire; y
- el uso de UIStackViews secundarias para reorganizar las subvistas en la rotación del dispositivo (consulte, por ejemplo, la clase SimpleExampleViewController en el código de muestra AdaptiveElements muestra en WWDC 2016 Session 233 ).
La nota técnica explica que la implementación subyacente de la autorotación implica aplicar una transformación directamente a la ventana de la aplicación:
La autorrotación se implementa aplicando una transformación de rotación a la ventana de la aplicación cuando el sistema determina que la interfaz debe rotar. Luego, la ventana ajusta sus límites para la nueva orientación y propaga este cambio hacia abajo a través de la jerarquía del controlador de vista a través de llamadas a cada controlador de vista y el controlador de presentación
viewWillTransitionToSize:withTransitionCoordinator:
método.
Tenga en cuenta que la aplicación de la cámara no excluye la autorrotación: cuando se utiliza la aplicación de la cámara, las orientaciones de Notification Center y Control Center reflejan la orientación del dispositivo. Es solo las vistas particulares dentro de la aplicación de la Cámara que parecen optar por la autorrotación. De hecho, esta respuesta sugiere que tales aplicaciones suelen utilizar las propiedades videoGravity
y videoOrientation
proporcionadas por clases de AVCaptureVideoPreviewLayer como AVCaptureVideoPreviewLayer y AVCaptureConnection .
El enfoque que debe tomar depende de si solo quiere evitar que la vista principal se gire o si desea que un controlador de vista de contenedor como UINavigationController no gire (es decir, bloquee la orientación del dispositivo).
Si es el primero, use la técnica descrita en la nota técnica para corregir la orientación de la vista principal, y (para iOS 9) ajuste las subvistas giratorias en un UIStackView y use la propiedad axis
para reorganizar las subvistas en la rotación del dispositivo, o (para iOS 8) gire manualmente las subvistas en la rotación del dispositivo (para ver cuál es la respuesta y el código de ejemplo ).
Si es lo último, el enfoque que está tomando está en el camino correcto. Para código de ejemplo, vea esta respuesta . Deberá compartir su código y más información acerca de la jerarquía de su controlador de vista para que podamos diagnosticar las anomalías relacionadas con el Centro de notificaciones.
Anexo : La razón por la cual las llamadas a shouldAutorotate
y shouldAutorotate
no se propagaron a los controladores de vista secundarios se explicó en la sección de Preguntas y respuestas técnicas QA1688 :
A partir de iOS 6, solo el controlador de vista superior (junto con el objeto
UIApplication
) participa en la decisión de rotar en respuesta a un cambio en la orientación del dispositivo. La mayoría de las veces, el controlador de vista que muestra su contenido es un elemento secundario deUINavigationController
,UITabBarController
o un controlador de vista de contenedor personalizado. Es posible que tenga que subclasificar un controlador de vista de contenedor para modificar los valores devueltos por sus métodossupportedInterfaceOrientations
shouldAutorotate
yshouldAutorotate
.
En Swift, puedes anular esos métodos usando extensiones .