How to access UIView in ViewController when calling function from another file? - ios

Situation: I paint stuff through a button-click on a subview. Then I add the subview to the view.
Problem: When calling drawNew from another swift file, the if-loop is not triggered. Probably because the view with the tag 777 does not get found.
Question: How do I tell the code to look for the view in ViewController?
When do I call the function drawNew()? I call it from another file with override func touchesEnded(...)
//###########################
// ViewController.swift
//###########################
var dv = Painting() // in here are functions to create a cg path
//
// IBActions
//
#IBAction func buttonPressed(_ sender: UIButton) {
// painting
if sender.tag == 1 {
drawNew()
} else if sender.tag == 2 {
CanvasView.clearCanvas()
}
}
//
// FUNCTIONS
//
func drawNew() {
dv.makeMeSomePath()
if let foundView = view.viewWithTag(777) { // problem lies here maybe
dv.tag = 111
foundView.addSubview(dv)
print("added subview")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// prepare the canvas for
let newView = CharacterView(frame: CGRect(x: 0,
y:0,
width: HandwritingCanvasView.frame.width,
height: HandwritingCanvasView.frame.height))
newView.tag = 777
HandwritingCanvasView.addSubview(newView)
// self.view.addSubview(demoView)
}
Then this is the other file from where I call the drawNew() func
// HandwritingCanvasView.swift
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
ViewController.drawNew()
}

It will be easier to have reference on newView in ViewController.
Try to do next
//###########################
// ViewController.swift
//###########################
weak var subView: UIView? //Add reference on your view
//
// FUNCTIONS
//
func drawNew() {
dv.makeMeSomePath()
if let foundView = subView { // Use the reference instead of searching view with tag
dv.tag = 111
foundView.addSubview(dv)
print("added subview")
}
}
override func viewDidAppear(_ animated: Bool) {
// Your code is here
// ...
HandwritingCanvasView.addSubview(newView)
subView = newView
//...
}

Related

Why are viewDidLayoutSubviews and viewWillLayoutSubviews called two times?

import UIKit
class ViewController: UIViewController {
// MARK: -property
// lazy var testBtn: UIButton! = {
// var btn: UIButton = UIButton()
// btn.backgroundColor = UIColor.red
// print("testBtn lazy")
// return btn
// }()
// MARK: -life cycle
override func viewDidLoad() {
super.viewDidLoad()
print("View has loaded")
// set the superView backgroudColor
// self.view.backgroundColor = UIColor.blue
// add testBtn to the superView
// self.view.addSubview(self.testBtn)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("View will appear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("View has appeared")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("View will disappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("View has desappeared")
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
print("SubViews will layout")
// layout subViews
// 'CGRectMake' is unavailable in Swift
// self.testBtn.frame = CGRectMake(100, 100, 100, 100)
// self.testBtn.frame = CGRect(x: 100, y: 100, width: 100, height: 100) // CGFloat, Double, Int
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("SubViews has layouted")
// let testBtn_Width = self.testBtn.frame.width
// print("testBtn's width is \(testBtn_Width)")
}
}
The result:
View has loaded
View will appear
SubViews will layout
SubViews has layouted
SubViews will layout
SubViews has layouted
View has appeared
As you see, I have created a new project and type some simple code.
I didn't change the size of the viewController's view.
Why are "SubViews has layouted" and "SubViews will layout" console two times?
Why are viewDidLayoutSubviews and viewWillLayoutSubviews called two times?
Because whenever setNeedsLayout or setNeedsDisplayInRect is being called internally, LayoutSubviews is also being called (once per run loop) on any given view. This applies for example if the view has been added, scrolled, resized, reused etc.

touchBegan and TouchEnded overrides are affecting another UIViewController

I have this UIViewController in which i've overrided the touchBegan and touchEnded functions. I also have a button that segueues (push) to another view controller with an SKView on it. But the overrided function in the first controller are still active ei. the calculations done there are still showing on the second ViewController.
Maybe theres something im missing or something im assuming thats wrong. Any help would be appreciated
This is the first view controller
import UIKit
import CoreMotion
class PortraitViewController: UIViewController {
var vc: UIViewController?
var startPoint: CGPoint?
var endPoint: CGPoint?
var movedPoint: CGPoint?
var previousMove = CGPoint(x: 0, y: 0)
var beginTouch: UITouch?
var scaleSum = 0
var isPortrait = true
let DEBUG: Bool = true
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .portrait}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .portrait}
override var shouldAutorotate: Bool {return false}
#IBAction func pinch(_ sender: UIPinchGestureRecognizer) {
if (isPortrait){
let scale = sender.scale
scaleSum += scale.exponent
print(scaleSum)
if(scaleSum > 10) {
scaleSum = 0
print(">10")
}
else if(scaleSum < -10) {
print("<10")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
endPoint = theTouch.location(in: self.view)
let diffx = (endPoint!.x - startPoint!.x)
let diffy = (endPoint!.y - startPoint!.y)
if(diffx != 0 && diffy != 0){
let vector = CGVector(dx: diffx, dy: diffy)
var angle = atan2(vector.dy, vector.dx) * CGFloat(180.0 / M_PI)
if angle < 0 { angle *= -1 } else { angle = 360 - angle }
}
}
}
super.touchesEnded(touches, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
startPoint = theTouch.location(in: self.view)
}
}
super.touchesBegan(touches, with: event)
}
//One of my attempts to jerry rig a solution
#IBAction func prepToLandscape(_ sender: UIBarButtonItem) {
self.isPortrait = false
print("isPortrait = \(self.isPortrait)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
This is the second view controller
import UIKit
import CoreMotion
import SpriteKit
class LandScapeViewController: UIViewController {
var vc: UIViewController?
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .landscape}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .landscapeLeft
// MARK:
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
let controllerStoryBoard = UIStoryboard(name: "Main", bundle: nil)
vc = controllerStoryBoard.instantiateViewController(withIdentifier: "Root")
// Part of my attempt to jerry rig a solution
let vcP: PortraitViewController = UIStoryboard(name:"Controller",bundle: nil).instantiateViewController(withIdentifier: "PortraitController") as! PortraitViewController
vcP.isPortrait = false
print("vcP.isPortrait = \(vcP.isPortrait)")
}
override func viewWillAppear(_ animated: Bool) {
self.view.isMultipleTouchEnabled = true
let scene = GameScene(size: joystickView.bounds.size)
scene.backgroundColor = .gray
if let skView = joystickView as? SKView {
skView.showsFPS = false
skView.ignoresSiblingOrder = true
skView.backgroundColor = .red
skView.presentScene(scene)
}
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func toPortrait(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: {() -> Void in
}
}
Assuming the code shown is everything relevant, this makes sense. What's happening is this: the touch events are hit-tested to the joystickView, but because you haven't implemented a custom touchesBegan(_:with:) on that view, the touch event is passed up to the next UIResponder in the responder chain. That would be the LandScapeViewController. But that class also doesn't implement a custom touchesBegan(_:with:), so the event passes to the next class, which in this case is PortraitViewController. Because PortraitViewController does implement that method, it gets called. There's your confusion.
To fix this, implement the touches… methods on UIResponder for either joystickView or LandScapeViewController, even if they do nothing – but don't call super in them! Note the following, from the touchesBegan(_:with:) documentation:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.
Where you're overriding touchesBegan(_:with:), you probably don't want to call super. This is because the super implementation is the one that says "oh, shoot, I don't know how to handle this – pass it up the chain!" But when you handle the touch, it should end there, because you're handling it! So only call super when you're not handling the touch – which in your case looks like never, at least for PortraitViewController.
For more information, check out Event Delivery: The Responder Chain.

Dismiss UIPopoverPresentationController with any gesture, not just tap

So I have a simple UIPopoverPresentationController that displays some content.
User can dismiss it by tapping anywhere on the screen (default popover behaviour).
I want the popover to be dismissed if the user does any kind of tap or gesture on the screen. Preferably drag gesture.
Any idea if this is possible? And how?
try using touchesBegan:withEvent method
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.view {
self.dismiss()
} else {
return
}
}
}
VC is the view presented in the popover.
in the presentViewController:animated:completion: block
[self presentViewController:vc animated:YES completion:^{
UIView *v1 = vc.view.superview.superview.superview;
for (UIView* vx in v1.subviews) {
Class dimmingViewClass = NSClassFromString(#"UIDimmingView");
if ([vx isKindOfClass:[dimmingViewClass class]])
{
UIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(closePopoverOnSwipe)];
[vx addGestureRecognizer:pan];
}
}
}];
you have a UIDimmingView that holds the tap gesture that will close. just add to it. I am using the Class dimmingViewClass = NSClassFromString(#"UIDimmingView"); to avoid making direct use of undocumented APIs. I have not tried yet to send this hack to apple, but will try next week. I hope it will pass. But I tested this and it did call my selector.
I resolved this problem using custom view:
typealias Handler = (() -> Void)?
final class InteractionView: UIView {
var dismissHandler: Handler = nil
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.dismissHandler?()
}
}
In the viewDidAppear configure this view and add to popover containerView:
fileprivate func configureInteractionView() {
let interactionView = InteractionView(frame: self.view.bounds)
self.popoverPresentationController?.containerView?.addSubview(interactionView)
interactionView.backgroundColor = .clear
interactionView.isUserInteractionEnabled = true
interactionView.dismissHandler = { [weak self] in
self?.hide()
}
}
fileprivate func hide() {
self.dismiss(animated: true, completion: nil)
}
my solution for this problem.
for example if you create a class UIViewController named MyPopoverViewController to present PopViewController.
then in the viewDidLoad() or viewWillAppear(_ animated:) Method add two GestureRecognizer as follows:
protocal MyPopoverControllerDelegate {
func shouldDismissPopover()
}
class MyPopoverViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// back trace to root view, if it is a UIWindows, add PanGestureRecognizer
// and LongPressGestureRecognizer, to dismiss this PopoverViewController
for c in sequence(first: self.view, next: { $0.superview}) {
if let w = c as? UIWindow {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(panGestureRecognizer)
let longTapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(longTapGestureRecognizer)
}
}
#objc private func dismissPopover(gesture: UIGestureRecognizer) {
delegate?.shouldDismissPopover()
}
}
then in your main ViewController, which this PopOverViewController presents, implements the Method of the Protocol.
extension YourMainViewController: MyPopoverControllerDelegate {
func shouldDismissPopover() {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}

Adding button over camera preview view [BESwiftCamera lib]

Within my swift app, I am using this library to build a custom video camera: https://github.com/bwearley/BESwiftCamera
In my view controller:
var camera:BESwiftCamera!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.attachCamera()
}
func attachCamera() {
do {
try self.camera.start()
} catch BESwiftCameraErrorCode.CameraPermission {
self.showCameraPermissionAlert()
} catch BESwiftCameraErrorCode.MicrophonePermission {
self.showMicrophonePermissionAlert()
} catch {
self.showUnknownErrorAlert()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let screenRect = UIScreen.mainScreen().bounds
// Configure Camera
self.camera = BESwiftCamera(withQuality: AVCaptureSessionPresetHigh, position: .Rear, videoEnabled: true)
self.camera.attachToViewController(self, withFrame: CGRectMake(0,0,screenRect.size.width,screenRect.size.height))
...
}
This works well. However, the issue is that it loads over the elements(buttons) I have in my storyboard for this particular view. How do I load my storyboard elements over the camera preview view?
Update the line in the lib
https://github.com/bwearley/BESwiftCamera/blob/master/BESwiftCamera/BESwiftCamera.swift#L139
vc.view.addSubview(self.view)
to
vc.view.insertSubview(self.view, atIndex: 0)

Button click crashes the whole application

I am currently building an application's UI without using IB or storyboard. I have the following hierarchy:
1. ViewController: which is the main view controller.
2. DrawerViewController: which is the view controller for the lower drawer menu.
The main view controller adds as a subview the drawerViewController's view.
3. Add Button: this is a UIButton created and added programatically to the drawerViewController's view.
The problem is if I clicked on the add button, the application crashes, giving absolutely no error messages in the output, except (lldb).
I have tried the following:
Change the selector name and the method name.
Checked the name of the selector method.
Added and removed the parameter of the selector method.
Changed the name of the button.
Added the button inside another view.
There's no way to trace the bug using breakpoints, because the application is compiling just fine, and the app crashes only if you click on the button.
Here's a link to a testing project I created to show the problem:
GitHub
ViewController:
class ViewController: UIViewController //, DrawerDelegate
{
//Lower Drawer.
var drawerView: DrawerView!
var drawerViewHidden: Bool = false
var addButton: UIButton!
var viewHeight: CGFloat! = 0
var viewWidth : CGFloat! = 0
var shownDrawerViewY: CGFloat!
var hiddenDrawerViewY: CGFloat!
init()
{
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Setup UI
setUpUI()
}
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK:- UI initialization.
/**
Private function that setsup the UI.
*/
private func setUpUI()
{
setUpDrawer()
}
//MARK: Drawer setup.
/**
A private function that programatically creataes a drawer and add it to the view.
*/
private func setUpDrawer()
{
var controller = DrawerViewController(width: self.view.frame.width, height: self.view.frame.height)
self.view.addSubview(controller.view)
//Hide
// toggleDrawer()
}
//MARK:- Delegates.
//MARK: DrawerDelegate methods.
func addButtonClicked(sender: UIButton)
{
// toggleDrawer()
}
}
DrawerViewController
class DrawerViewController: UIViewController
{
//Drawer variables.
var drawerViewHidden: Bool = false
var addPinDrawer: AddPinDrawer!
var viewHeight: CGFloat! = 0
var viewWidth : CGFloat! = 0
var shownDrawerViewY: CGFloat?
var hiddenDrawerViewY: CGFloat?
var addButton: UIButton?
init(width: CGFloat, height: CGFloat)
{
super.init(nibName: nil, bundle: nil)
viewWidth = width
viewHeight = height
setUpUI()
}
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
required override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
{
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
//MARK:- UI setup
func setUpUI()
{
setUpDimensions()
setUpDrawerElements()
// toggleDrawer()
}
func setUpDimensions()
{
//Determine the y in the case drawer is shown
shownDrawerViewY = viewHeight * 2.0/8.0
//Determine the height of the drawer.
let drawerHeight = viewHeight * 6.0/8.0
//Determine the y in the case drawer is hidden
hiddenDrawerViewY = viewHeight * 7.4/8.0
//Create the frame, starting with the drawer shown.
let frame = CGRectMake(0, shownDrawerViewY!, viewWidth, drawerHeight)
//Create a new Drawer View.
self.view = UIView(frame: frame)
setUpAddButton(frame)
//Setup the background image of the drawer.
self.view.backgroundColor = UIColor(patternImage: UIImage(named: Constants.drawerBackgroundImage)!)
}
func setUpDrawerElements()
{
//Setup the button.
setUpAddPinDrawer()
}
func setUpAddPinDrawer()
{
addPinDrawer = AddPinDrawer(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height))
// self.view.addSubview(addPinDrawer)
}
//MARK: Handling drawer toggles
func toggleDrawer()
{
//Toggle the variable.
drawerViewHidden = !drawerViewHidden
//If the drawer must be hidden.
if drawerViewHidden
{
hideDrawer()
}
//If the drawer must be shown
else
{
showDrawer()
}
}
func hideDrawer()
{
//Hide the drawer
UIView.animateWithDuration(0.6, animations: { () -> Void in
self.view.frame.origin.y = self.hiddenDrawerViewY!
})
// drawerView.hideDrawer()
}
func showDrawer()
{
UIView.animateWithDuration(0.6, animations: { () -> Void in
self.view.frame.origin.y = self.shownDrawerViewY!
})
// drawerView.showDrawer()
}
func setUpAddButton(frame: CGRect!)
{
//Determine the button dimensions.
let width:CGFloat = 75.0//1.0/10.0 * viewHeight
let x = frame.width/2.0 - width/2.0
//Button background image.
let background = UIImage(named: Constants.addButtonBackgroundImage)!
//Create the button.
addButton = UIButton(frame: CGRectMake(x, -width/2.0, width, width)) as UIButton
// addButton!.setImage(background, forState: UIControlState.Normal)
addButton?.backgroundColor = UIColor.yellowColor()
//Add the event handler.
addButton!.addTarget(self, action: "buttonAdd:", forControlEvents: .TouchUpInside)
//Set it rotated.
// self.addButton!.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
//Add the button to the subview.
self.view.addSubview(addButton!)
// println(addButton!.targetForAction("test", withSender: addButton)!)
}
func buttonAdd(sender: UIButton)
{
println("Helloooo asdf")
}
}
I have solved the problem. There's a very little mistake in the initiation of the DrawerViewController, In the main view controller, I am creating a variable called DrawerViewController and initiating its view. This variable will be deallocated once the method is done, which means all events will not be handled. Causing the app to crash since there's no method to handle the button's events.
The solution:
Make the DrawerViewController an instance variable and initialise it in the main view controller.

Resources