UIButton visible background - swift

Can't find any useful information about custom view field on UIButton
I need to create a subclass of UIButton with big button frame and small visible part of it
Also I need to animate background on button tap so I need to set button background as a backgroundImage
Here is my code:
public final class BigSmallButton: UIButton {
...
public override var buttonType: UIButton.ButtonType {
return .custom
}
override init(frame: CGRect) {
super.init(frame: frame)
NSLayoutConstraint(item: self,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1.0,
constant: 44).isActive = true
let imageSize = CGSize(width: 24, height: 24)
let backgroundImage = UIImage.image(with: Constants.buttonBackgroundColor, size: imageSize)
setBackgroundImage(backgroundImage, for: .normal)
layer.cornerRadius = Constants.buttonIconSize.height / 2.0
layer.masksToBounds = true
layer.frame = CGRect(x: 0, y: 0, width: bounds.width, height: imageSize.height)
}
And then add to NavigationController
let bigSmallButton = BigSmallButton(frame: .zero)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bigSmallButton)
It works but my button height becomes equal to 24.0 not 44.0
I need answer about is it possible to make such button?
And if it's possible then what I’m doing wrong?

The only way that I found is overriding button function point(inside point: CGPoint, with event: UIEvent?)
Something like this:
let hitFrame: CGRect
override init(frame: CGRect) {
super.init(frame: frame)
hitFrame = CGRect(x: frame.minX, y: frame.minY - 10.0, width: frame.width, height: frame.height + 10.0)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return hitFrame.contains(point)
}

Related

How can I pass the value of a property to its subclass UICollectionViewCell?

I'm triying to build a Pinterest Layout Programmatically but I can't pass the value of the image height from the UICollectionViewLayoutAttributes to the override init (subclass UICollectionViewCell) I'm not sure if the problem is with the initialization of properties.
How can I pass this value to the Subclass??
The thing is that the cell has a Annotation height, for that reason I need the value of the image Height to constraint the image. I would appreciate if someone could help me :)
class PinterestCell: UICollectionViewCell {
var imageHeight: CGFloat
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = 13
return imageView
}()
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if let attributes = layoutAttributes as? PinterestLayoutAttributes {
imageHeight = attributes.imageHeight
}
}
override init(frame: CGRect) {
super.init(frame: frame)
let imageViewHeightLayoutConstraint =
NSLayoutConstraint(
item: imageView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: imageHeight
)
imageView.anchor(top: topAnchor, left: leftAnchor, right: rightAnchor)
imageView.addConstraint(imageViewHeightLayoutConstraint)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I think the issue here that you are creating imageViewHeightLayoutConstraint in the init using imageHeight but when you change imageHeight you did not change the constrain constant to the new value.

my navigation bar is not visible in my app

I am trying to set up the a navigation bar and a WKWebview in my app, the webview is present but i am not able to see the navigation bar is not visible. What am i doing wrong?
I edited my code now it functions well this is the problem remaining.
This is my code
class MyController:UIViewController, WKNavigationDelegate{
var webView: WKWebView!
#IBOutlet weak var barView: UIView!
required init(coder aDecoder: NSCoder) {
self.webView = WKWebView(frame: CGRect.zero)
super.init(coder: aDecoder)!
self.webView.navigationDelegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
barView.frame = CGRect(x:0, y: 0, width: view.frame.width, height: 40)
view.insertSubview(webView, belowSubview: barView)
webView.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: webView, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: webView, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 1, constant: 0)
view.addConstraints([height, width])
if((UserDefaults.standard.object(forKey:"language")as? String ?? "") == "en"){
let url = URL(string: "myurl")!
webView.load(URLRequest(url: url))
}
else{
let url = URL(string: "https://myurl")!
webView.load(URLRequest(url: url))
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
barView.frame = CGRect(x:0, y: 0, width: size.width, height: 40)
}
override func viewWillAppear(_ animated: Bool) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnSideMenuActn(_ sender: UIButton) {
self.pushViewControl(ViewControl:"SideMenuController")
}
}

How can I add a UIBezierPath to a UIView that uses auto layout?

In my project I want to add a line to a UIView (this UIView uses auto layout).
I'm using UIBezierPath and CAShapeLayer to draw the line.
This is my code:
let myView = UIView()
self.view.addSubView(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
myView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
myView.heightAnchor.constraint(equalToConstant: 40).isActive = true
myView.widthAnchor.constraint(equalToConstant: 100).isActive = true
myView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: myView.frame.height/2))
path.addLine(to: CGPoint(x: myView.frame.width, y: myView.frame.height/2))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 11.0
myView.layer.addSublayer(shapeLayer)
But my problem is no line shows in viewController.
When I use this code everything is OK and the line shows perfectly:
let myView = UIView(frame: CGRect(x: 0, y: 160, width: 100, height: 40))
self.view.addSubView(myView)
How can I solve this problem? I must use auto layout.
Your best bet is to use a custom UIView subclass and set your layer's path in layoutSubviews(). That way you get the proper frame when needed.
Here's a simple example:
class LineView: UIView {
let shapeLayer: CAShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(shapeLayer)
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 11.0
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))
shapeLayer.path = path.cgPath
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView = LineView()
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
NSLayoutConstraint.activate([
myView.topAnchor.constraint(equalTo: self.view.topAnchor),
myView.heightAnchor.constraint(equalToConstant: 40),
myView.widthAnchor.constraint(equalToConstant: 100),
myView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
])
// if you want to see the frame of myView
//myView.backgroundColor = .yellow
}
}
Result - with yellow background so we can see the frame, and with your constraints (you probably want to use safeArea...):
I think I found your problem. You have defined a UIView with a frame of (0, 0, 0, 0). If you then draw a line in that view with the coordinates of that line related to that view, you get no line.
I assume that you want your view to be on 0, 160 with the height of 40 and width of 100. The line should then run from (0, 20) to (100, 20).
In that respect are your constraints not rightly set, since your view ends up in the top right outside the screen.
I made the following code that might help. You set your constraints how you like it.
Make a custom view (MyLineView) like this:
import UIKit
class MyLineView: UIView {
private let lineColor = UIColor.black
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
lineColor.setStroke()
path.lineWidth = 11
path.move(to: CGPoint(x: self.frame.origin.x, y: self.frame.size.height / 2))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height / 2))
path.stroke()
print(self.frame)
}
}
Then you can code your view controller like this:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let myView = UIView(frame: CGRect(x: 0, y: 160, width: 100, height: 40))
let myView = MyLineView()
let viewPositionX: CGFloat = 0
let viewPositionY: CGFloat = 160
let viewWidth: CGFloat = 100
let viewHeight: CGFloat = 40
let topConstraint = viewPositionY + viewHeight - self.view.frame.size.height
let widthConstraint = viewWidth - self.view.frame.size.width
myView.backgroundColor = UIColor.green
self.view.addSubview(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: viewPositionX).isActive = true
NSLayoutConstraint(item: myView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: viewPositionY).isActive = true
NSLayoutConstraint(item: myView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: topConstraint).isActive = true
NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: widthConstraint).isActive = true
}
}
I just added the green background color to see where the view is. This works on my phone. Hope this helps.

UIView.animate works fine in viewDidAppear() but fires instantly anywhere else

I have looked and tried every solution I could find online as to why my animations is not properly firing. DISCLOSURE: they work fine when put in viewDidAppear(). This question has been asked countless times but none of the solutions work. The results of the animations appear but not with the delay specified, they are instant. What I would like is to fade in a my requestRideButton in and out using either isHidden with UIView.transition or alpha with UIView.animate.
I would also like to move the button while it is fading. I have tried every combination of self.view.layoutIfNeeded() both in and out the animate closure with the constraint in and out. I have also tried it with self.view.superview?.layoutIfNeeded()
class RideRequestViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var rideRequestBottomButtonConstraint: NSLayoutConstraint!
#IBOutlet weak var rideRequestButtonTopConstraint: NSLayoutConstraint!
// Views
#IBOutlet var mapView: MKMapView!
#IBOutlet var rideDetailsView: UIView!
#IBOutlet var requestRideButton: UIButton!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Layout map view to fill screen
mapView.frame = view.bounds
// Apply corner radius and shadow styling to floating views
let cornerRadius: CGFloat = 5.0
inputContainerView.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)
originButton.layoutCornerRadiusMask(corners: [.topLeft, .topRight], cornerRadius: cornerRadius)
paymentButton.layoutCornerRadiusMask(corners: .bottomLeft, cornerRadius: cornerRadius)
priceButton.layoutCornerRadiusMask(corners: .bottomRight, cornerRadius: cornerRadius)
rideDetailsView.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)
pilotView.layoutCornerRadiusMask(corners: [.topLeft, .bottomLeft], cornerRadius: cornerRadius)
vehicleView.layoutCornerRadiusMask(corners: [.topRight, .bottomRight], cornerRadius: cornerRadius)
requestRideButton.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)
}
override func viewDidLoad() {
ref = Database.database().reference()
}
func animate() {
self.rideRequestButtonTopConstraint.constant = -44
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
#IBAction
private func handleRequestRideButtonTapped() {
switch rideRequestState {
case .none:
// Update to requesting state
rideRequestState = .requesting
// Perform payment request
paymentContext.requestPayment()
case .requesting:
// Do nothing
// Show button
break
case .active:
// Complete the ride
completeActiveRide()
}
}
private func reloadRequestRideButton() {
guard originPlacemark != nil && destinationPlacemark != nil && paymentContext.selectedPaymentMethod != nil else {
// Show disabled state
requestRideButton.backgroundColor = .riderGrayColor
requestRideButton.setTitle("CONFIRM DELIVERY", for: .normal)
requestRideButton.setTitleColor(.black, for: .normal)
requestRideButton.setImage(nil, for: .normal)
requestRideButton.isEnabled = false
return
}
animate() // <--- view just disappears instantly instead
switch rideRequestState {
case .none:
// Show enabled state
requestRideButton.backgroundColor = .riderYellowColor
requestRideButton.setTitle("CONFIRM DELIVERY", for: .normal)
requestRideButton.setTitleColor(.black, for: .normal)
requestRideButton.setImage(nil, for: .normal)
requestRideButton.isEnabled = true
case .requesting:
// Show loading state
requestRideButton.backgroundColor = .riderYellowColor
requestRideButton.setTitle("...", for: .normal)
requestRideButton.setTitleColor(.white, for: .normal)
requestRideButton.setImage(nil, for: .normal)
requestRideButton.isEnabled = false
case .active:
// Show completion state
requestRideButton.backgroundColor = .white
requestRideButton.setTitle("Complete Ride", for: .normal)
requestRideButton.setTitleColor(.riderDarkBlueColor, for: .normal)
requestRideButton.setImage(nil, for: .normal)
requestRideButton.isEnabled = true
}
}
Changes to views to be animated should be inside the animate block.
func animate() {
UIView.animate(withDuration: 1) {
self.rideRequestButtonTopConstraint.constant = -44
self.view.layoutIfNeeded()
}
}
Changing the constraint in the line above will just update a value and move the view. It won't animate it
UPDATE
So based on looking at another answer and some additional research, The author of UIKit suggests that we update constraints and call layoutIfNeeded inside the animation block like above.
Here is the playground I used to test it
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let movableView = UIView()
var topConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
view.addSubview(button)
button.setTitle("Animate", for: .normal)
button.addTarget(self, action: #selector(animate), for: .touchUpInside)
view.addSubview(movableView)
movableView.backgroundColor = .red
movableView.translatesAutoresizingMaskIntoConstraints = false
topConstraint = NSLayoutConstraint(item: movableView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 100)
topConstraint?.isActive = true
NSLayoutConstraint(item: movableView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: movableView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100).isActive = true
NSLayoutConstraint(item: movableView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100).isActive = true
}
#objc func animate() {
UIView.animate(withDuration: 4.0, animations: {
self.topConstraint?.constant = -100
self.view.layoutIfNeeded()
})
}
}
let vc = ViewController()
PlaygroundPage.current.liveView = vc.view

How do I left align the title of a navigation bar in Xcode?

I've been trying the following in order to get the title of a navigation bar left aligned:
In the AppDelegate.swift file:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UINavigationBar.appearance().barTintColor = UIColor.red
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName:
UIColor.white]
return true
}
In a TableViewController.swift file:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.navigationBar.topItem?.title = "Home"
}
but nothing I find solves the problem. I also tried the following that I found on here which does not show anything:
in the AppDelegate.swift file:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UINavigationBar.appearance().barTintColor = UIColor.red
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName:
UIColor.white]
let lbNavTitle = UILabel (frame: CGRect(x: 0, y: 40, width: 320, height: 40))
lbNavTitle.center = CGPoint(x: 160, y: 285)
lbNavTitle.textAlignment = .left
lbNavTitle.text = "Home"
self.navigationItem.titleView = lbNavTitle
In a TableViewController.swift file:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.navigationBar.topItem?.title = "Home"
let lbNavTitle = UILabel (frame: CGRect(x: 0, y: 0, width: 320, height: 40))
lbNavTitle.backgroundColor = UIColor.red
lbNavTitle.textColor = UIColor.black
lbNavTitle.numberOfLines = 0
lbNavTitle.center = CGPoint(x: 0, y: 0)
lbNavTitle.textAlignment = .left
lbNavTitle.text = "Home"
let string = NSMutableAttributedString ("Title/nSubTitle")
self.navigationItem.titleView = lbNavTitle
}
let label = UILabel()
label.textColor = UIColor.white
label.text = "TCO_choose_reminder".localized;
self.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: label)
I init a UIBarButtonItem with the constructor that gets a UIView and set my desired label as parameter to get the following.
Edit:
If you do not want the back button to be removed. Set the following flag.
self.navigationItem.leftItemsSupplementBackButton = true
Keep in mind that having a label + Back button (with title) would not look cool.In this case I replace the default back button with an arrow asset.
You can use the navigationItems titleView to add a UILabel with left alignment and then set its frame using auto layout like this:
let label = UILabel()
label.text = "Title Label"
label.textAlignment = .left
self.navigationItem.titleView = label
label.translatesAutoresizingMaskIntoConstraints = false
label.superview?.addConstraint(NSLayoutConstraint(item: label, attribute: .centerX, relatedBy: .equal, toItem: label.superview, attribute: .centerX, multiplier: 1, constant: 0))
label.superview?.addConstraint(NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: label.superview, attribute: .width, multiplier: 1, constant: 0))
label.superview?.addConstraint(NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: label.superview, attribute: .centerY, multiplier: 1, constant: 0))
label.superview?.addConstraint(NSLayoutConstraint(item: label, attribute: .height, relatedBy: .equal, toItem: label.superview, attribute: .height, multiplier: 1, constant: 0))
Well, if you'd like to use large title, then it is left aligned by defaut.
For whole navigation hierarchy.
navigationController?.navigationBar.prefersLargeTitles = true
For 1 viewController
navigationItem.largeTitleDisplayMode = .always
If u want a simple left aligned text that covers the entire titleView area heres my solution guys!.
func setLeftAlignTitleView(font: UIFont, text: String, textColor: UIColor) {
guard let navFrame = navigationController?.navigationBar.frame else{
return
}
let parentView = UIView(frame: CGRect(x: 0, y: 0, width: navFrame.width*3, height: navFrame.height))
self.navigationItem.titleView = parentView
let label = UILabel(frame: .init(x: parentView.frame.minX, y: parentView.frame.minY, width: parentView.frame.width, height: parentView.frame.height))
label.backgroundColor = .clear
label.numberOfLines = 2
label.font = font
label.textAlignment = .left
label.textColor = textColor
label.text = text
parentView.addSubview(label)
}
This works for me (iOS 13+):
let offset = UIOffset(horizontal: -CGFloat.greatestFiniteMagnitude, vertical: 0)
navigationController?.navigationBar.standardAppearance.titlePositionAdjustment = offset
navigationController?.navigationBar.scrollEdgeAppearance?.titlePositionAdjustment = offset
navigationController?.navigationBar.compactAppearance?.titlePositionAdjustment = offset