Propagation of UITouch events - swift

I have views arranged as shown in attached figure.
MAINVIEW is an added subview to UISCROLLVIEW
class NotewallController:UIViewController, UIScrollViewDelegate {
var bgImage:UIImageView?
var bgScrollView:UIScrollView?
var masterView:UIView?
override func viewDidLoad() {
self.bgScrollView = UIScrollView(frame: self.view.bounds)
self.bgScrollView!.contentSize = CGSizeMake(1000, 1000)
self.bgScrollView!.delegate = self
self.bgScrollView!.minimumZoomScale = 1.0
self.bgScrollView!.maximumZoomScale = 4.0
self.view.addSubview(self.bgScrollView!)
masterView = UIView(frame: CGRectMake(0,0,1000,1000))
masterView!.backgroundColor = UIColor.clearColor()
masterView!.userInteractionEnabled = true
bgScrollView!.addSubview(masterView!)
let note = WALLVIEW(frame: CGRectMake(20, 20, 30, 30))
self.masterView!.addSubview(note)
}
WALLVIEW is a subclass of UIVIEW
class WallView:UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let bgImageView = UIImageView(frame: frame)
bgImageView.image = UIImage(named: "stickynote.png")
bgImageView.userInteractionEnabled = true
self.addSubview(bgImageView)
let tap = UITapGestureRecognizer(target: self, action: "WallNoteTapped:")
bgImageView.addGestureRecognizer(tap)
}
My issue is in WALLVIEW. As you can notice, I have a TAP GESTURE in WALLVIEW. When the program is executed, a tap of WALLVIEW object doesn't call the "WallNoteTapped:" method. Suspecting the touch events are not passed to WALLVIEW object "note".
I am not able to understand why "WallNoteTapped:" method is not triggered.
Thanks
EDIT:
I made WALLVIEW subclass of UIImageView rather than UIView and it worked finally.

Adding an UITapGestureRecognizer cancel te touches in its subviews.
Try to set cancelsTouchesInView to false.

Related

UISlider not updating values

apologies if this is a stupid question. I can't seem to get my slider to update its value as its being interacted with. (I'm going to point everyone to the very last method in this long code)
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = 7
return rect
}
}
class FactionButton: CustomSlider {
var factionSlider = CustomSlider(frame: CGRect(x: 15, y: 542, width: 386, height: 57))
func factionBalanceSlider(){
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = false
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)), for: .valueChanged)
}
func getSlider() -> CustomSlider {
return factionSlider
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = 10
return customBounds
}
#objc func recordFactionBalance(sender: CustomSlider){
//also calculates balance and adds it into the quiz data
print("hi")
print(sender.value) //It's this part that doesn't work
}
}
It's this bit nearest to the bottom that has the issue. (Everything else is fine) The action function doesn't seem to be triggered at all, even when I'm interacting with it. Neither print statements are being executed. Any ideas why?
Cheers
From the getSlider(), i can guess you are using this class as a utility to get the CustomSlider. So, i suspect you are adding the slider to the view as below,
let container = FactionButton()
container.factionBalanceSlider()
let slider = container.getSlider()
self.view.addSubview(slider)
If you will not add the container to the view which is set as the receiver for .valueChange event so it will not get any event. To receive events you also need to add the container in the view as below,
self.view.addSubview(container)

Button anchored to InputAccessoryView working only in its frame

Setup
I implemented an inputAcessoryView in a UIViewController via the following:
override var inputAccessoryView: UIView? {
get {
return bottomBarView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
fileprivate lazy var bottomBarView: UIView = {
let view = UIView()
let separatorLineView = UIView()
view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height / 9)
view.backgroundColor = .white
/...
separatorLineView.backgroundColor = UIColor(r: 220, g: 220, b: 220, a: 1)
separatorLineView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(separatorLineView)
separatorLineView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
separatorLineView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
separatorLineView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
view.addSubview(self.photoButton)
self.photoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
self.photoButton.centerYAnchor.constraint(equalTo: separatorLineView.centerYAnchor, constant: -12).isActive = true
self.photoButton.widthAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true
self.photoButton.heightAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true
return view
}()
photoButton implements an action on .touchUpInside.
The issue
The button is only clickable in the part included in inputAccessoryView's frame: if I click on it on the part that's outside, nothing happens.
Is there any quick way to make it work like so ?
I have tried moving the photoButton out of bottomBarView and adding it to the view instead however:
I need photoButton to be "anchored" to the inputAccessoryView, but it seems inputAccessoryView doesn't have anchor properties from within the view ? (I can only access their frame properties via bang unwrapping)
When I do so, photoButton is partly hidden behind inputAccessoryView and I can't bring it forward with view.bringSubview(ToFront:)
Thanks in advance.
The issue you have is that the inputAccessoryView (bottomBarView for you) has no height. All of the subviews you're adding are therefore outside of its bounds. If you were to set clipsToBounds = true, you would see that everything disappears.
The issue with this, as you're seeing, is that those subviews aren't getting touch events since they're outside the bounds of their superview. From Apple's docs:
If a touch location is outside of a view’s bounds, the hitTest:withEvent: method ignores that view and all of its subviews. As a result, when a view’s clipsToBounds property is NO, subviews outside of that view’s bounds are not returned even if they happen to contain the touch.
Understanding Event Handling, Responders, and the Responder Chain
The solution is to get the inputAccessoryView to be the correct height. self.view.frame.height / 9 is 0 in your example, since the frame has not been set by the time you're using it.
Instead, create a UIView subclass, with the following important parts:
class BottomBarView: UIView {
init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
let someView = UIView()
addSubview(someView)
someView.translatesAutoresizingMaskIntoConstraints = false
someView.topAnchor.constraint(equalTo: topAnchor).isActive = true
someView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
someView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
someView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
// In this case, the top, bottom, and height constraints will
// determine the height of the inputAccessoryView
someView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: 0)
}
}
Try adding a button. You'll see that now it gets a touch event, since it's within the bounds of it's superview now.
I just had this problem myself in my own work over the past couple days.
You need to implement your own hitTest UIResponder method for your inputAcessoryView. Something that might look like this:
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
if self.photoButton.frame.contains(point) {
return self.photoButton
}
return super.hitTest(point, with: event)
}
More information can be seen in this related question.

Swift - Visual constraints not working

I have a scrollView which I instantiate programaticaly. What I want to do is to add constraints so it will look good in both horizontal and vertical orientation. The problem that constraints not working
How it looks vertically - good
How it looks horizontally - not good
Code is following
class FAPhoto: UIViewController, UIScrollViewDelegate {
var imageURLsArray = [String]()
var imageViews:[UIImageView] = []
var arrayOfPhotos = [Photo]()
var scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: self.view.frame)
view.addSubview(scrollView)
scrollView.contentSize = CGSizeMake(view.frame.size.width * CGFloat(arrayOfPhotos.count), scrollView.frame.size.height)
scrollView.pagingEnabled = true
for (var i = 0; i < arrayOfPhotos.count; i++) {
var imageView = UIImageView()
imageView.frame = CGRectMake(CGFloat(i) * view.frame.size.width, scrollView.frame.origin.y, scrollView.frame.size.width, scrollView.frame.size.height)
imageView.contentMode = .ScaleAspectFit
let image = imageView.hnk_setImageFromURL(NSURL(string: arrayOfPhotos[i].url!)!)
scrollView.addSubview(imageView)
}
}
override func viewWillAppear(animated: Bool) {
}
override func viewWillLayoutSubviews() {
let bindings = Dictionary(dictionaryLiteral: ("scrollView", self.scrollView))
let horizontalConstraints =
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-0-[scrollView]-0-|",
options: [],
metrics: nil,
views: bindings)
self.view.addConstraints(horizontalConstraints)
let verticalConstraints =
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-0-[scrollView]-0-|",
options: [],
metrics: nil,
views: bindings)
self.view.addConstraints(verticalConstraints)
}
}
The problem is that you set contentSize of scrollView and imageView's frames in viewDidLoad. This is OK for portrait mode, but when view rotates to landscape, it's frame also changes, so you have to update these values accordingly. I think you have 3 opportunities here.
Track rotation events and update contentSize of scrollView and imageView's frames, when view rotates.
Use Autolayout for scrollView. You will not have to calculate contentSize or set imageView's frames, just set constraints once in viewDidLoad. Here you can find some examples https://stackoverflow.com/a/20232911/4757335.
Use UICollectionView instead of UIScrollView. It handles rotation much easier.

Can I add a UIImageView to an UIInputViewController in Swift?

I'm working on a custom keyboard and want to add some UIImageViews to the interface. I can load a .Xib fine as shown below, but when I try to create a UIImage within the main view it causes it to crash. Do you know why this is?
import UIKit
class KeyboardViewController: UIInputViewController {
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
let myView = NSBundle.mainBundle().loadNibNamed("KeyboardView", owner: nil, options: nil)[0] as UIView
self.view.addSubview(myView)
let image = UIImage(named: "grid.png")
let imageSize = CGSize(width: 216, height: 216)
var gridImageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize))
gridImageView.image = image
//tried this- Crashes
self.addSubview(gridImageView)
//also tried this- Crashes
self.view.insertSubview(view: gridImageView, aboveSubview: myView)
}
If I manually add a ImageView to the .XIB, it also crashes. I feel like UIInputViewController will not let you use a Image View, even though the documentation would indicate it's possible. I'm usually an idiot with these things so am probably misunderstanding the docs:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/inputView

create scrollview programmatically in swift without outlet

I have tried enough but i don't understand what is wrong with code, it was working fine when I created an outlet, but then I found out that i don't need an outlet, so want to create it just programmatically.
var BestPractices = UIScrollView(frame: CGRectMake(0, 94, 768, 924))
BestPractices.hidden = true
I cannot access the properties of "BestPractices" in viewController , while same code works fine in playground
This is an answer from this question.
.
class ScrollingViewController : UIViewController {
// Create a scrollView property that we'll set as our view in -loadView
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
override func loadView() {
// calling self.view later on will return a UIView!, but we can simply call
// self.scrollView to adjust properties of the scroll view:
self.view = self.scrollView
// setup the scroll view
self.scrollView.contentSize = CGSize(width:1234, height: 5678)
// etc...
}
func example() {
let sampleSubView = UIView()
self.view.addSubview(sampleSubView) // adds to the scroll view
// cannot do this:
// self.view.contentOffset = CGPoint(x: 10, y: 20)
// so instead we do this:
self.scrollView.contentOffset = CGPoint(x: 10, y: 20)
}
add this line to identify scroll bar at last of loadview().
scrollView.backgroundColor = UIColor.blue