How to use Apple TV Remote in landscape? - device-orientation

Is there a tvOS API to detect landscape orientation and/or allow use of the Apple TV controller for touch gesture input with the controller held in landscape? (e.g. so the app will get a swipe up event with respect to the floor/gravity whether the controller is portrait or turned sideways).

I've played a little bit around with the AppleTV and the remote. Gesture recognition on the remote is possible. I've copied the sample code from my Blog article about this.
For detecting the orientation of the remote this answer from the Apple Developer forum might be helpful.
import SpriteKit
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed:"Spaceship")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Add Sprite
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(sprite)
// Register Swipe Events
let swipeRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedRight:"))
swipeRight.direction = .Right
view.addGestureRecognizer(swipeRight)
let swipeLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedLeft:"))
swipeLeft.direction = .Left
view.addGestureRecognizer(swipeLeft)
let swipeUp:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedUp:"))
swipeUp.direction = .Up
view.addGestureRecognizer(swipeUp)
let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedDown:"))
swipeDown.direction = .Down
view.addGestureRecognizer(swipeDown)
}
// Handle Swipe Events
func swipedRight(sender:UISwipeGestureRecognizer){
sprite.position = CGPoint(x: sprite.position.x + 10, y: sprite.position.y)
}
func swipedLeft(sender:UISwipeGestureRecognizer){
sprite.position = CGPoint(x: sprite.position.x - 10, y: sprite.position.y)
}
func swipedUp(sender:UISwipeGestureRecognizer){
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y+10)
}
func swipedDown(sender:UISwipeGestureRecognizer){
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y-10)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}

Related

Why FirebaseVisionImage.fromMediaImage() produces OutOfMemoryError

CameraX is build, analyze() method is called and an image is passed and then closed (deleted) with close() method. From this image FirebaseVisionImage is created and passed for processing (text recognition). Code samples and code labs differs and not implement TextRecognition with CameraX or using old API versions.
override fun analyze(imageProxy: ImageProxy) {
if (isValidText) {
imageProxy.close()
return
}
val mediaImage = imageProxy.image // requires annotation
val degrees = imageProxy.imageInfo.rotationDegrees
val rotation = rotationDegreesToFirebaseRotation(degrees)
if (mediaImage != null) {
runTextRecognition(mediaImage, rotation) // line 44
}
imageProxy.close()
}
private fun runTextRecognition(mediaImage: Image, rotation: Int) {
// Create FirebaseVisionImage from frame
val visionImage = FirebaseVisionImage.fromMediaImage(mediaImage, rotation) // line 64
val recognizer = FirebaseVision.getInstance()
.onDeviceTextRecognizer
recognizer.processImage(visionImage)
.addOnSuccessListener { texts ->
processTextRecognitionResult(texts!!, recognizer)
if (isValidText) {
recognizer.close()
return#addOnSuccessListener
}
}
.addOnFailureListener { e -> // Task failed with an exception
e.printStackTrace()
}
}
In my project I'm using this dependencies
def firebase_version = '24.0.2'
def camerax_version = '1.0.0-beta02'
implementation "com.google.firebase:firebase-ml-vision:$firebase_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha09"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
and this is how I build CameraX
private fun bindPreview(cameraProvider: ProcessCameraProvider) {
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder?.display?.getRealMetrics(it) }
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
val rotation = viewFinder?.display?.rotation
// Set up the preview use case to display camera preview
val preview = Preview.Builder()// Request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation!!)
.build()
// Choose the camera by requiring a lens facing
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val executor = Executors.newSingleThreadExecutor()
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
val imageAnalyzer = ImageAnalysis.Builder()
// Request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalyzer.setAnalyzer(executor, analyzer)
var camera = cameraProvider.bindToLifecycle(viewFinder?.context as LifecycleOwner, cameraSelector, preview, imageAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview.setSurfaceProvider(viewFinder?.createSurfaceProvider(camera.cameraInfo))
}
I was able to resolve the issue by switching to mlkit.
First update the app/build.gradle file to use mlkit instead of firebase:
// Add ML Kit dependencies
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.1.0'
Next update the analyzer to use InputImage:
#androidx.camera.core.ExperimentalGetImage
private class TextAnalyzer(private val listener: TextListener) : ImageAnalysis.Analyzer {
override fun analyze(imageProxy: ImageProxy) {
val mediaImage: Image = imageProxy.image ?: return
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
runTextRecognition(image)
imageProxy.close()
}
Then update runTextRecognition to:
private fun runTextRecognition(image: InputImage) {
val recognizer = TextRecognition.getClient()
recognizer.process(image)
...
}
That should do it.
Here is the codelab that gives more details.

Back button not being called in TabbarCoordinator in horizontal flow iOS 12

Coordinator pattern is an old topic with many libraries trying to solve it and I am learning it in simple example app.
My current set up is 3 rootViewControlers: LoadingStateCoordinator, WelcomeCoordinator, TabBarCoordinator but missing connection between UIKit and coordinators. I am trying to implement it with a UINavigationController but the button is not being called. I need a way to connect to back button and a reusable coordinator that I could push to and dellocate accordingly (that is without RxSwift).*Set up Welcome screen as the parent/main navigation and always be able to come back to it.**
So after user selects a form from modal view (vertical flow) presented I show on a push a TabBarCoordinator (horizontal). All viewControllers have empty.storyboard, UIViewController and Coordinator exept the TabBar.Here I only have a coordinator due to the set up of child tab coordinators and the magic needs to happen on a back button tap. Currenly this only being called when user comes from LoadingStateCoordinator. There I need to send the user back to the Welcome screen so they can change the onboarding set up. Here is the first code for LoadingStateCoordinator:
final class LoadingStateCoordinator: NSObject, Coordinator {
*// MARK: - Inputs required*
var childCoordinators: [Coordinator]
var presenter: UINavigationController
private let window: UIWindow
*// MARK: - Initialization*
init(window: UIWindow) {
self.window = window
childCoordinators = []
presenter = UINavigationController()
}
*// MARK: - Coordinator*
func start() {
let controller: LoadingStateViewController = LoadingStateViewController.instantiate()
window.rootViewController = controller
controller.delegate = self
}
}
*// MARK: - LoadingViewControllerDelegate*
extension LoadingStateCoordinator : LoadingViewControllerDelegate {
func performScreenSwitch() {
if UserDefaults.standard.userWasHere == false {
let tabCoordinator: TabBarCoordinator = TabBarCoordinator(window: window, tabBarController: UITabBarController())
window.rootViewController = presenter
addChildCoordinator(tabCoordinator)
tabCoordinator.start()
presenter.pushViewController(tabCoordinator.tabBarController!, animated: true)
} else {
let welcomeCoordinator = WelcomeCoordinator(window: window, presenter: presenter)
window.rootViewController = welcomeCoordinator.presenter
addChildCoordinator(welcomeCoordinator)
welcomeCoordinator.start()
}
}
}
And here is the TabBarCoordinator that need to perform back to Welcome screen action. When I present popToRootfunction it pushes the Welcome screen but all the button there are disbled. I guess to be retain cycle issue. Do I need funadametally another set up? Is there a way to popToRoot(vc) in this set up? What I tryed ended with runtime error "poping to non existing controller".
TabBarCoordinator code that need to perform this:
final class TabBarCoordinator: NSObject, Coordinator {
internal var presenter: UINavigationController
internal var tabBarController: UITabBarController?
internal var childCoordinators: [Coordinator]
var parentCoordinator: LoadingStateCoordinator?
lazy var leftBtn: UIBarButtonItem = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "arrow.turn.up.left"), for: .normal)
button.sizeToFit()
button.addTarget(self,
action: #selector(self.popToRoot(_:)),
for: .touchUpInside)
return UIBarButtonItem(customView: button)
}()
init(window: UIWindow, tabBarController: UITabBarController) {
self.tabBarController = tabBarController
childCoordinators = []
self.presenter = UINavigationController()
}
func start() {
performGetTabBar()
self.presenter.delegate = self
}
private func performGetTabBar() {
let coordinators: [Coordinator] = generateTabCoordinators()
coordinators.forEach({ coordinator in
coordinator.start()
addChildCoordinator(coordinator)
})
let presenters: [UIViewController] = coordinators.map({ coordinator -> UIViewController in
return coordinator.presenter
})
leftBtn.style = .plain
tabBarController?.navigationItem.leftBarButtonItem = leftBtn
tabBarController?.setViewControllers(presenters, animated: false)
selectTab(type: SurfTripCoordinator.self)
}
private func generateTabCoordinators() -> [Coordinator] {
let calculatorCoordinator: CalculatorCoordinator = CalculatorCoordinator(presenter: UINavigationController())
let tripCoordinator: SurfTripCoordinator = SurfTripCoordinator(presenter: UINavigationController())
let sellCoordinator: SavedTripsCoordinator = SavedTripsCoordinator(presenter: UINavigationController())
return [calculatorCoordinator, tripCoordinator, sellCoordinator]
}
*//this is not being called when coming from vertical flow*
#objc func popToRoot(_ sender: UIBarButtonItem) {
let storyboard: UIStoryboard = UIStoryboard(name: Constants.Storyboards.welcomeViewCoordinator, bundle: nil)
let controller: WelcomeViewController = WelcomeViewController.instantiate(from: storyboard)
tabBarController?.navigationController?.pushViewController(controller, animated: true)
}
}
extension TabBarCoordinator: UINavigationControllerDelegate {
func selectTab<T: Coordinator>(type _: T.Type) {
guard let index = childCoordinators.firstIndex(where: { coordinator in
coordinator is T
}) else {
return
}
tabBarController?.selectedIndex = index
}
}
and here is the current WelcomeCoordinator set up
class WelcomeCoordinator: NSObject, Coordinator {
internal var presenter: UINavigationController
var childCoordinators: [Coordinator]
init(window: UIWindow, presenter: UINavigationController) {
self.presenter = presenter
childCoordinators = []
}
func start() {
let storyboard: UIStoryboard = UIStoryboard(name: Constants.Storyboards.welcomeViewCoordinator, bundle: nil)
let controller: WelcomeViewController = WelcomeViewController.instantiate(from: storyboard)
controller.delegate = self
presenter.pushViewController(controller, animated: true)
}
}
extension WelcomeCoordinator : WelcomeViewControllerDelegate {
func performAddLevel() {
let addLevelCoordinator: AddLevelViewCoordinator = AddLevelViewCoordinator(presenter: UINavigationController())
addLevelCoordinator.start()
addChildCoordinator(addLevelCoordinator)
addLevelCoordinator.presenter.modalPresentationStyle = .fullScreen
presenter.present(addLevelCoordinator.presenter, animated: true, completion: nil)
}
}
sorry for the long post I wish there was more reaktive native way to do this...
Ok so I found partlly a solution the back button solution for my case: not using pushViewController or show because it comes with back button. presenter.setViewControllers([tabCoordinator.tabBarController!], animated: true) and there setting the navBar to hidden. I made my own navItem button to navigate to rootVC. Next step to allocate and remove all child tabBar coordinators on back tap recognized.

Corner Radius Not Working?

im trying to setup a circle image view and when I set the corner radius to perform the operation it does absolutely nothing. I've looked at various threads and solutions none worked
import UIKit
class AlterProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view?.backgroundColor = UIColor.white
navigationItem.title = "Profile Settings"
view.addSubview(selectProfileImage)
///Constraints for all views will go here
_ = selectProfileImage.anchor(view.centerYAnchor, left: view.leftAnchor, bottom: nil, right: nil, topConstant: -275, leftConstant: 135, bottomConstant: 0, rightConstant: 0, widthConstant: 100, heightConstant: 100)
// selectProfileImage.layer.cornerRadius = selectProfileImage.frame.size.width/2
///////////////////////////////////////////////
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Where all buttons and labels will be added
//will just be a nice looking image view to be next to the profile settings button
lazy var selectProfileImage: UIImageView = {
let selectPicture = UIImageView()
// self.selectProfileImage.layer.cornerRadius = self.selectProfileImage.frame.size.width / 2;
selectPicture.image = UIImage(named: "Paris")
// selectPicture.layer.cornerRadius = selectPicture.frame.size.width / 2;
selectPicture.clipsToBounds = true
selectPicture.translatesAutoresizingMaskIntoConstraints = false
selectPicture.contentMode = .scaleAspectFill
selectPicture.layer.shouldRasterize = true
selectPicture.layer.masksToBounds = true
return selectPicture
}()
///////////////////////////////////////////////////////////////////////////////////
}
None of the methods seem to work im actually kind of stumped right now
Given that you layout with AutoLayout I would suspect the image view simply doesn't have the correct size when you calculate the radius. The image view is initialized with a size of 0,0 and thus the calculated radius will be 0 as well. Instead, move the radius calculation in viewDidLayoutSubviews after calling super:
func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
selectProfileImage.layer.cornerRadius = selectProfileImage.frame.size.width / 2;
selectProfileImage.layer.masksToBounds = true
}

What is wrong with my constraints?

I am simply trying to create a view with a contained view that is 50% as tall and high. In the following code, I set constraints to achieve that when I add the view to the super-view. I set translatesAutoResizingMaskToConstraints to false, add the constraints via anchors. I also tried to call setNeedsUpdateConstraints, and add the same constraints in updateConstraints.
But in the following Playground code, I don't see the constrained subview testView. I expect to see an blue view half the size of the orange view, but all i see is orange.
I'm not sure what I am missing here.
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class LView: UIView {
var testView = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .orange
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
addSubview(testView)
testView.backgroundColor = .blue
testView.text = "....."
//testView.setNeedsUpdateConstraints()
testView.translatesAutoresizingMaskIntoConstraints = false
testView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5).isActive = true
testView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true
testView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
testView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
let testView = LView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = testView

Trying to get button to spin in WatchKit

the code i'm using works just fine in swift for iPhone apps but not in the WatchKit 7.0 beta. the outlets and actions are different. I'm not sure what needs to change to make it work in WatchKit. please help!
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
#IBOutlet var spinButton: WKInterfaceButton!
var isRotating = false
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func spinAction() {
if !isRotating {
// create a spin animation
let spinAnimation = CABasicAnimation()
// starts from 0
spinAnimation.fromValue = 0
// goes to 360 ( 2 * π )
spinAnimation.toValue = M_PI*2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make it spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// and the animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
spinButton.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
spinButton.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}
You are limited to a subset of all the APIs available on iOS when developing for the watchOS.
If you want to do basic animations try out a WKInterfacePicker and change images when the digital crown is moved.
IBOutlet WKInterfacePicker *myPicker;
- (void)willActivate {
[super willActivate];
WKPickerItem *item1 = [[WKPickerItem alloc] init];
item1.contentImage = [WKImage imageWithImageName:#"Unknown.png"];
WKPickerItem *item2 = [[WKPickerItem alloc] init];
item2.contentImage = [WKImage imageWithImageName:#"Unknown-2.png"];
[self.myPicker setItems:array];
}
When the value exceeds the array count start over from index 0.
- (IBAction)myPickerAction:(NSInteger)value {
if (value % 2 == 0) {
[self.myPicker setSelectedItemIndex:-1];
}
}
This will make the WKInterfacePicker change between your images when the digital crown is rotated.

Resources