UIView "Stuck", not responding to selectors - ios

I'm adding a custom UIView to my HomeViewController's view like so:
var customView: CustomView!
func addCustomView() {
customView = CustomView(frame: CGRect(x: 0, y: 0, width: Int(view.frame.width), height: 150.0))
customView.translatesAutoresizingMaskIntoConstraints = false
customView.delegate = self
view.addSubview(customView)
self.view.addConstraint(NSLayoutConstraint(item: customView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[view]-20-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view": customView]));
}
This custom view has a button that segues to a modal view controller, and sets the current view controller as its delegate. When I dismiss the modal view controller, it calls its delegate method:
func modalVCDidCreateThing(thing: PFObject) {
customView.removeFromSuperview()
}
The customView is not being removed from the superview.
I've checked that it's not nil
After performing the removeFromSuperview, logging out the object shows that it still exists
The code is running on the main thread
The view doesn't respond to other commands either. For example, I tried setting it's hidden property to false, but the view does not hide. Logging out the view showed that the property was set.
I tried view.setNeedsLayout after removeFromSuperview. Didn't work.
Super frustrated by this. Thanks.

Related

How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:
extension UIStoryboard {
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
viewController.view.addSubview(myCustomSubview)
}
}
}
This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.
Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.
Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:
Method 1 - Add constraint directly from "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
, toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)
viewController.view.addSubview(myCustomSubview)
viewController.view.addConstraint(top)
}
}
Method 2 - Add constraint outlets and setter method in MyCustomSubview
class MyCustomSubview: UIView {
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
func setConstraints(){
self.widthConstraint.constant = UIScreen.main.bounds.size.width
self.heightConstraint.constant = 20
}
}
And call setter method in "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
myCustomSubview.setConstraints()
viewController.view.addSubview(myCustomSubview)
}
}
(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)
Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.
When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:
Here's some sample code that makes a view fill it's parent view:
parentView.addSubview(yourView)
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
Edit: I've actually come around to perferring this method of adding NSLayoutConstraints, even though the results are the same
NSLayoutConstraint.activate([
yourView.topAnchor.constraint(equalTo: parentView.topAnchor),
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
])
For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit
Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:
class MyCustomSubview: UIView {
func setConstraints(withRelationshipTo view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
// Replace with your own custom constraints
self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let view = self.view // Added for clarity
view.addSubview(myCustomSubview)
myCustomSubview.setConstraints(withRelationshipTo: view)
}
}
}
You can use this extension for anywhere you're going to add a subview to a existing UIView.
extension UIView {
func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
var constraints : [NSLayoutConstraint] = []
if left {
let constraintLeft = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
constraints.append(constraintLeft)
}
if top {
let constraintTop = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
constraints.append(constraintTop)
}
if right {
let constraintRight = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
constraints.append(constraintRight)
}
if bottom {
let constraintBottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
constraints.append(constraintBottom)
}
self.addConstraints(constraints)
}
}
You can call this method like this:
containerView.setConstraintsFor(contentView: subView!, top: false)
This will add subView to the containerView and constraint to all sides except top side.
You can modify this method to pass left, top, right, bottom Constant value if you want.

View size is automatically being set to window size, when one of window's subview is removed

I have two views one of them is the mainVideoView (Grey color) and the other is the subVideoView (red color). Both of them are the subViews of UIApplication.shared.keyWindow.
When I try and minimise them (using func minimiseOrMaximiseViews, they get minimised (as shown in the below image).
After which, I would like to remove the subVideoView from the window. The moment I try and remove the subVideoView (using func removeSubVideoViewFromVideoView() called from func minimiseOrMaximiseViews), the mainVideoView enlarges to the full screen size, I am not sure why this is happening, I want it to stay at the same size.
Could someone please advise/ suggest how I could achieve this ?
This is how I am setting up the views
func configureVideoView(){
videoView.backgroundColor = UIColor.darkGray
videoView.tag = 0 // To identify view during animation
// ADDING TAP GESTURE TO MAXIMISE THE VIEW WHEN ITS SMALL
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
videoView.addGestureRecognizer(tapGestureRecognizer)
// ADDING PAN GESTURE RECOGNISER TO MAKE VIDEOVIEW MOVABLE INSIDE PARENT VIEW
videoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))
guard let window = UIApplication.shared.keyWindow else{
return
}
window.addSubview(videoView)
videoView.translatesAutoresizingMaskIntoConstraints = false
let viewsDict = ["videoView" : videoView] as [String : Any]
// SETTING CONSTRAINTS
window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[videoView]|", options: [], metrics: nil, views: viewsDict))
window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[videoView]|", options: [], metrics: nil, views: viewsDict))
window.layoutIfNeeded() // Lays out subviews immediately
window.bringSubview(toFront: videoView) // To bring subView to front
print("Videoview constraints in configureVideoView \(videoView.constraints)")
}
func configureSubVideoView(){
subVideoView.backgroundColor = UIColor.red
subVideoView.tag = 1 // To identify view during animation
subVideoView.isHidden = true
// Adding Pan Gesture recogniser to make subVideoView movable inside parentview
subVideoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))
// Constraining subVideoView to window to ensure that minimising and maximising animation works properly
constrainSubVideoViewToWindow()
}
func constrainSubVideoViewToWindow(){
//self.subVideoView.setNeedsLayout()
guard let window = UIApplication.shared.keyWindow else{
print("Window does not exist")
return
}
guard !window.subviews.contains(subVideoView)else{ // Does not allow to go through the below code if the window already contains subVideoView
return
}
if self.videoView.subviews.contains(subVideoView){ // If videoView contains subVideoView remove subVideoView
subVideoView.removeFromSuperview()
}
window.addSubview(subVideoView)
// Default constraints to ensure that the subVideoView is initialised with maxSubVideoViewWidth & maxSubVideoViewHeight and is positioned above buttons
let bottomOffset = buttonDiameter + buttonStackViewBottomPadding + padding
subVideoView.translatesAutoresizingMaskIntoConstraints = false
let widthConstraint = NSLayoutConstraint(item: subVideoView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewWidth)
let heightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewHeight)
let rightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1.0, constant: -padding)
let bottomConstraint = NSLayoutConstraint(item: subVideoView, attribute: .bottom, relatedBy: .equal, toItem: window, attribute: .bottom, multiplier: 1.0, constant: -bottomOffset)
var constraintsArray = [NSLayoutConstraint]()
constraintsArray.append(widthConstraint)
constraintsArray.append(heightConstraint)
constraintsArray.append(rightConstraint)
constraintsArray.append(bottomConstraint)
window.addConstraints(constraintsArray)
window.layoutIfNeeded() // Lays out subviews immediately
subVideoView.setViewCornerRadius()
window.bringSubview(toFront: subVideoView) // To bring subView to front
}
This is how I am animating views and removing subVideoView
func minimiseOrMaximiseViews(animationType: String){
let window = UIApplication.shared.keyWindow
let buttonStackViewHeight = buttonsStackView.frame.height
if animationType == "maximiseView" {
constrainSubVideoViewToWindow()
}
UIView.animate(withDuration: 0.3, delay: 0, options: [],
animations: { [unowned self] in // "[unowned self] in" added to avoid strong reference cycles,
switch animationType {
case "minimiseView" :
self.hideControls()
// Minimising self i.e videoView
self.videoView.frame = CGRect(x: self.mainScreenWidth - self.videoViewWidth - self.padding,
y: self.mainScreenHeight - self.videoViewHeight - self.padding,
width: self.videoViewWidth,
height: self.videoViewHeight)
// Minimising subVideoView
self.subVideoView.frame = CGRect(x: self.mainScreenWidth - self.minSubVideoViewWidth - self.padding * 2,
y: self.mainScreenHeight - self.minSubVideoViewHeight - self.padding * 2,
width: self.minSubVideoViewWidth,
height: self.minSubVideoViewHeight)
window?.layoutIfNeeded()
self.videoView.setViewCornerRadius()
self.subVideoView.setViewCornerRadius()
print("self.subVideoView.frame AFTER setting: \(self.subVideoView.frame)")
default:
break
}
}) { [unowned self] (finished: Bool) in // This will be called when the animation completes, and its finished value will be true
if animationType == "minimiseView" {
// **** Removing subVideoView from videoView ****
self.removeSubVideoViewFromVideoView()
}
}
}
func removeSubVideoViewFromVideoView(){
//self.subVideoView.setNeedsLayout()
guard let window = UIApplication.shared.keyWindow else{
return
}
guard !self.videoView.subviews.contains(subVideoView)else{ // Does not allow to go through the below code if the videoView already contains subVideoView
return
}
if window.subviews.contains(subVideoView) { // removing subVideoView from window
subVideoView.removeFromSuperview() // *** CAUSE OF ISSUE ***
}
guard !window.subviews.contains(subVideoView) else{
return
}
videoView.addSubview(subVideoView)
}
The videoView and subVideoView are layout by Auto Layout. In Auto Layout if you want to change the frame, you have to update the constraints.
Calling removeFromSuperView will removes any constraints that refer to the view you are removing, or that refer to any view in the subtree of the view you are removing. This means the auto layout system will to rearrange views.

Add constraint before view is seen?

I am trying to center an arrow button with a width = 40 in the middle of my screen using constraints programmatically.
My code works if I call it in viewDidAppear but crashes in viewDidLoad.
Having it in viewDidAppear is an eye sore since you see the screen and then see the button jump to the middle of the screen. Any idea how to make it set up before the view is shown?
I might add this is a subview so I am calling centerArrow() from a viewController whose view is not the same view.
So inside the viewController:
let pview = NSBundle.mainBundle().loadNibNamed("Profile", owner: nil, options: nil)[0] as! ProfileView
override func viewDidAppear(animated: Bool) {
pview.centerArrow()
}
And inside ProfileView which inherits UIView:
func centerArrow()
{
let screenSize: CGRect = UIScreen.mainScreen().bounds
let width = (screenSize.width / 2) - 20
let constraint = NSLayoutConstraint(item: arrowButton, attribute: NSLayoutAttribute.LeftMargin, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.LeftMargin, multiplier: 1, constant: width)
self.addConstraint(constraint)
}
Try using viewDidLayoutSubViews, that might provide the timing you are looking for in this case.

UIWebView rendering web pages in a low-quality zoomed in state

Using UIWebView to render Third party websites. I cant use WKWebView for various reasons.
There is no Xib/View in storyboard associated with my ViewController. All the UI is loaded in the viewDidLoad() method
let navigationBar = UINavigationBar()
navigationBar.setTranslatesAutoresizingMaskIntoConstraints(false)
navigationBar.barStyle = UIBarStyle.Black
//added navigation items
self.view.addSubview(navigationBar)
UXHelper.createHorizontalConstraints(navigationBar, outerView: self.view, margin: 0)
UXHelper.createConstraint(navigationBar, parent: self.view, to: self.view, constraint: NSLayoutAttribute.Top, margin: 0)
UXHelper.createConstraint(navigationBar, parent: self.view, to: nil, constraint: NSLayoutAttribute.Height, margin: AlgoLinkWebViewController.navBarHeight)
self.webView = UIWebView()
self.webView.backgroundColor = UIColor.whiteColor()
self.webView.opaque = false
self.webView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.webView.delegate = self
self.view.addSubview(self.webView)
UXHelper.createHorizontalConstraints(self.webView, outerView: self.view, margin: 0)
UXHelper.createConstraint(self.webView, parent: self.view, to: self.view, constraint: NSLayoutAttribute.Bottom, margin: 0)
self.view.addConstraint(NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: navigationBar, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0))
With the above code below is the screenshot of the webView rendered
The expected WebView rendering I am looking for is . This is from a different app that I know uses UIWebView but don't have access to code
NOTE: PLEASE ignore the navigation bar differences
The options I tried are
remove Autoresizing = false
Removing this did not remove the zoomed in state
webView.scalesWebPagesToFit = true
setting this did not make a difference
Initializing UIWebView with the parent view frame size
self.webView = UIWebView(frame: self.view.bounds) - no difference
Set the scroll view scale value to 0.5
did not make a difference
Maybe you use zoom mode at your iPhone6/6plus.
If you want to implement changes in the font size you can use Java Script to do so by adding the fallowing in your viewDidLoad method
var fontSize = 104 //Your preferred font size go here
var jsString = "document.getElementsByTagName('body')[0].style.fontSize='\(fontSize)px'"
webView.stringByEvaluatingJavaScriptFromString(jsString)
webView.stringByEvaluatingJavaScriptFromString(jsString)
I hope that helps!

iOS .addConstraint only works once

I'm making a custom keyboard for iOS8 and on the Apple Developer docs it says that you can change the height of a custom keyboard anytime after the initial primary view draws on the screen. And it says to do this you should use the .addConstaint() method.
Here's a link:
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/Keyboard.html
I'm using Swift. The initial height of the keyboard is 215 pixels. I have a swipe up gesture that increases the height to 350 pixels. Which works as expected. And a swipe down that changes the height to 300 pixels.
That all works ok, but the problem is it only works once. I swipe up and the height increases, I swipe down and it decreases, but if I swipe up again nothing happens. If I swipe down again nothing happens.
So I'd be greatful if anyone could take a look at my two functions and tell me what I'm doing wrong.
Here's the code:
// IBActions
#IBAction func action1(sender: AnyObject) {
if topboxvisible == false {
topboxvisible = true
UIView.animateWithDuration(0.08, delay: 0, options: .CurveEaseIn, animations: {
self.topbox.frame.offset(dx: 0, dy: 40)
}, completion: nil)
}
let expandedHeight:CGFloat = 300
let heightConstraint = NSLayoutConstraint(item:self.view,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 0.0,
constant: expandedHeight)
self.view.removeConstraint(heightConstraint)
self.view.addConstraint(heightConstraint)
}
#IBAction func action2(sender: AnyObject) {
if topboxvisible == true {
topboxvisible = false
UIView.animateWithDuration(0.08, delay: 0, options: .CurveEaseOut, animations: {
self.topbox.frame.offset(dx: 0, dy: -40)
}, completion: nil)
}
let expandedHeight:CGFloat = 350
let heightConstraint = NSLayoutConstraint(item:self.view,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 0.0,
constant: expandedHeight)
self.view.removeConstraint(heightConstraint)
self.view.addConstraint(heightConstraint)
}
Your removeConstraint calls aren't doing anything, because the constraint you just created (in the previous line of code) isn't in the view. So what you're doing here is adding more constraints every time, from what I can see. I imagine this is leading to multiple conflicting constraints—do you see any NSConstraint warnings popping out in the console log while you're running the code?
Try this: create both your height constraints in advance, and store them in instance variables. In action1, remove the 350-high constraint, and add the 300. In action2, remove the 300, and add the 350.

Resources