how can I add auto layout constraints for a line programatically - swift3

I drew a line programatically in y UI. How can I add constraints for this line.
let line = UIView(frame: CGRect(x: 10, y: 350, width: 350, height: 1))
line.backgroundColor = UIColor.white;self.view.addSubview(line)

I created a red line: height = 1, top = 50 and the width will be flexible You can modify the position as you want.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let line = UIView()
line.backgroundColor = UIColor.red
view.addSubview(line)
line.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: line, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 20.0)
let topConstraint = NSLayoutConstraint(item: line, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 50.0)
let trailingConstraint = NSLayoutConstraint(item: view, attribute: .right, relatedBy: .equal, toItem: line, attribute: .right, multiplier: 1.0, constant: 20.0)
let heightConstraint = NSLayoutConstraint(item: line, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 1.0)
view.addConstraints([topConstraint, leadingConstraint, trailingConstraint, heightConstraint])
}
The result!

NSLayoutConstraint Style
NSLayoutConstraint(item: line, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: line, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0).active = true`

Related

How to add buttons to wkWebView?

Because we make wkWebView as the root view, the button I added on my layout doesn't appear on screen. Then I tried to add wkWebView as a subview of the root view, but the app crashes. Then I found about UIBarButtonItem, but it seems that you could only create 2 buttons.
I need to add 5 buttons anywhere on the screen. Is there a workaround for this? I'm a newbie on ios development and I'm not that good at using the interface builder.
After trying #Matic Oblak's code and placing it on loadView() where I do my wkwebview initialization, I'm getting an error saying "Warning: Attempt to present SomeViewController on WebViewController whose view is not in the window hierarchy!
This is my current code.
override func loadView() {
// let contentController: WKUserContentController = WKUserContentController()
// let config: WKWebViewConfiguration = WKWebViewConfiguration()
//
// contentController.add(self, name: "callbackHandler")
// config.userContentController = contentController
//
// webView = WKWebView(
// frame: UIScreen.main.bounds,
// configuration: config
// )
//webView.navigationDelegate = self
//webView.translatesAutoresizingMaskIntoConstraints = false
//webView.scrollView.isScrollEnabled = false
//view = webView
if let webViewContainer = webviewContainer {
// Initialize:
let contentController: WKUserContentController = WKUserContentController()
let config: WKWebViewConfiguration = WKWebViewConfiguration()
contentController.add(self, name: "callbackHandler")
config.userContentController = contentController
let webView = WKWebView(frame: webViewContainer.bounds, configuration: WKWebViewConfiguration()) // Create a new web view
webView.translatesAutoresizingMaskIntoConstraints = false // This needs to be called due to manually adding constraints
webView.navigationDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
webView.scrollView.isScrollEnabled = false
// Add as a subview:
webViewContainer.addSubview(webView)
// Add constraints to be the same as webViewContainer
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .leading, relatedBy: .equal, toItem: webViewContainer, attribute: .leading, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .trailing, relatedBy: .equal, toItem: webViewContainer, attribute: .trailing, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: webViewContainer, attribute: .top, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: webViewContainer, attribute: .bottom, multiplier: 1.0, constant: 0.0))
// Assign web view for reference
self.webView = webView
}
}
override func viewDidLoad() {
if(someVar1.isEmpty && someVar2.isEmpty){
showSomeDialog()
}
}
The first commented lines on loadView() are my previous code for reference.
After placing #Matic Oblak's code on viewDidLoad(), I'm getting a black screen. Also, after placing the code that calls SomeViewController on viewDidAppear(), the error is gone but I'm still getting the black screen.
Actually WKWebView has a bit of a nasty bug that it crashes when added in storyboard. But you can do that in code. What I usually do is create a view in storyboard just as a web view panel (usually full screen but not if you have some tool bars) then just add a new web view as a subview. I still use constraints to add the web view:
import UIKit
import WebKit
class WebViewController: UIViewController {
#IBOutlet private var webViewContainer: UIView? // A view on which a web view will be added to
private var webView: WKWebView? // Web view manually added
override func viewDidLoad() {
super.viewDidLoad()
if let webViewContainer = webViewContainer {
// Initialize:
let webView = WKWebView(frame: webViewContainer.bounds, configuration: WKWebViewConfiguration()) // Create a new web view
webView.translatesAutoresizingMaskIntoConstraints = false // This needs to be called due to manually adding constraints
// Add as a subview:
webViewContainer.addSubview(webView)
// Add constraints to be the same as webViewContainer
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .leading, relatedBy: .equal, toItem: webViewContainer, attribute: .leading, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .trailing, relatedBy: .equal, toItem: webViewContainer, attribute: .trailing, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: webViewContainer, attribute: .top, multiplier: 1.0, constant: 0.0))
webViewContainer.addConstraint(NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: webViewContainer, attribute: .bottom, multiplier: 1.0, constant: 0.0))
// Assign web view for reference
self.webView = webView
}
}
}
So here webViewContainer is dragged from storyboard as any view you want. It can even be the main view of the view controller if you want to.
The rest should be explained in the comments.
So using this procedure you don't need to "hack" adding subviews on top of everything. You can add your button normally in your storyboard.
Note: The problem with using webViewContainer as main view is that web view will be added later than the rest of views so it may cover those you added in storyboard. You need to either send the web view to back or bring overlaying items to front. It is just adding webViewContainer.sendSubviewToBack(webView) but I still prefer to use a separate view just for web view.
You could add floating button on top of your viewcontroller by using some third party library. Let's say using Floaty or with any other similar.

Tap gesture not working after constraints added

My code works perfectly fine until constraints are added.Once constraints are added tap gesture doesn't seem to work.
My code is as below:
//this is in viewDidLoad()
containerView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(containerView)
self.view.addSubview(scrollView)
if #available(iOS 11.0, *) {
let top = NSLayoutConstraint.init(item: containerView, attribute: .top, relatedBy: .equal, toItem: self.view.safeAreaLayoutGuide, attribute: .top, multiplier: 1.0, constant: 0.0)
let leading = NSLayoutConstraint.init(item: containerView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailing = NSLayoutConstraint.init(item: containerView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let height = NSLayoutConstraint.init(item: containerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 60.0)
NSLayoutConstraint.activate([top, leading, trailing, height])
} else {
// Fallback on earlier versions
}
self.automaticallyAdjustsScrollViewInsets = false
//This is inside a function where I create subviews
containerView.addSubview(DiaryList)
let tapGesture = AddMyTapGesture(target: self, action: #selector(handleTap))
tapGesture.title = date
tapGesture.titleFormat = dateFormat
DiaryList.addGestureRecognizer(tapGesture)
DiaryList.isUserInteractionEnabled = true
//this is the tap gesture
#objc func handleTap(sender: AddMyTapGesture) {
print("tapped")
}

Xcode ViewController elements is not interactive after animation

I have a project that need to have a top sheet, that when user scrolling down it will reveal. so I created 2 separate ViewController in storyboard like this:
in the main ViewController I add the sheet view controller as a subview like this:
let topSheet = self.storyboard?.instantiateViewController(withIdentifier: "top_sheet_vc") as! TopSheetVC
self.addChildViewController(topSheet)
self.view.addSubview(topSheet.view)
topSheet.didMove(toParentViewController: self)
topSheet.view.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: topSheet.view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: Constant.TOP_SHEET_HEIGHT)
let leading = NSLayoutConstraint(item: topSheet.view, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: topSheet.view, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0)
let bottom = NSLayoutConstraint(item: topSheet.view, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0)
NSLayoutConstraint.activate([height, leading, trailing, bottom])
Then I have a UIPanGestureRecognizer that attached to the main view of main view controller, and in the handler method I can move the view so the top sheet can be revealed, like this:
func panGestureHappened(recognizer: UIPanGestureRecognizer) {
let y = self.view.frame.minY
let translation = recognizer.translation(in: self.view)
let min = fmin(y + translation.y, Constant.TOP_SHEET_HEIGHT)
self.view.transform = CGAffineTransform(translationX: 0, y: min)
// or I can change the frame, same result as CGAffineTransform
// self.view.frame = CGRect(x: CGFloat(0), y: min, width: self.view.frame.width, height: self.view.frame.height)
}
so far so good, the top sheet reveals and become visible and the main view is shifting down as I expected.
The Problem is that the top sheet is not interactive and clickable and I can't figure out what is wrong with this.
so any help or tip would be appreciated.
tnx in advance.
NOTE: If I animate the TopSheet not the base view, so the top sheet overlap the base view, and not shifting down the base view the problem will be gone and TopSheet is clickable. But I can't figure out why.
UPDATE: I eliminate all the custom constraints and added a ContainerView in the main controller inside storyboard, and the container has negative value for top to bring it outside, and when the main view get animated and top is revealed But it is still not clickable, BUT if I overlay the container it became clickable again, so there is something related to view height, any idea?

Set the center of custom MKAnnotationView

I am using the custom MKAnnotationView. I use the autolayouts which are working fine, but the position(frame) of annotation view is not appropriate.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)
{
if view.annotation is MKUserLocation
{
return
}
let starbucksAnnotation = view.annotation as! LiquorLocation
let views = Bundle.main.loadNibNamed("CustomCalloutView", owner: nil, options: nil)
let calloutView = views?[0] as! CustomCalloutView
calloutView.labelLiquorShopName.text = starbucksAnnotation.title
calloutView.labelLiquorShopAddress.text = starbucksAnnotation.address
calloutView.labelLiquorShopAwayDistance.text = starbucksAnnotation.locationKmAway
calloutView.imageViewLiquorShop.image = starbucksAnnotation.liquorShopIcon
calloutView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(calloutView)
mapView.setCenter((view.annotation?.coordinate)!, animated: true)
}
What I am doing wrong? I want to center the annotation view at the top of the pin.
Autolayouts again save my day. I add the layout constraint between my calloutview and view(MKAnnotationView). I write below lines underneath view.addSubview(calloutView) in didSelect method.
let horizontalConstraint = NSLayoutConstraint(item: calloutView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: calloutView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: calloutView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: calloutView.frame.size.width)
view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint])

Swift 3 UICollectionView reusable section header will not display

In swift 3 with Xcode 8.2 I can't get a reusableHeaderView to display with UICollectionView. I placed breakpoints at various points in my header view file and they are never tripped. Here is my code.
import UIKit
class WishListViewController: UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
var collectionView: UICollectionView!
var wishListItems = [[String:Any]]()
var shops = [[String: Any]]()
let cellId = "WishListCell"
let headerId = "WishListHeaderView"
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
let imageAspectRatio: CGFloat = 1.2
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.main.bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenWidth, height: ((screenWidth / 3) * imageAspectRatio) + 20)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.estimatedItemSize.height = ((screenWidth / 3) * imageAspectRatio) + 20
layout.estimatedItemSize.width = screenWidth
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(WishListCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(WishListHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
// Do any additional setup after loading the view.
}
func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: headerId,
for: indexPath) as! WishListHeaderView
headerView.textLabel.text = shops[indexPath.section]["shop_name"] as? String
return headerView
default:
assert(false, "Unexpected element kind")
}
}
Here is my header view file.
import UIKit
class WishListHeaderView: UICollectionReusableView {
var textLabel: UILabel
let screenWidth = UIScreen.main.bounds.width
override init(frame: CGRect) {
textLabel = UILabel()
super.init(frame: frame)
self.addSubview(textLabel)
textLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
textLabel.textAlignment = .left
textLabel.numberOfLines = 0
textLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
textLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
NSLayoutConstraint(item: textLabel, attribute: .leading, relatedBy: .equal,
toItem: self, attribute: .leadingMargin,
multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: textLabel, attribute: .trailing, relatedBy: .equal,
toItem: self, attribute: .trailingMargin,
multiplier: 1.0, constant: 0.0),
])
self.backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You need to implement height for header function for your collection view.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
referenceSizeForHeaderInSection section: Int) -> CGSize
then return the height you want it to be.