I am working on my iOS-App in Swift and I've already finished my Android-App. On Android, I already found a solution to display just a part of a webpage. This is what I want to do to my iOS-app, too.
So, for those who are familiar with android developing, it was this solution which helped me:
How to display a part of the webpage on the webview android
But I asked my question in a way that it is possible to answer this question even if you don't have experience with android developing!
You can assume that, as this android solution does, my webpage is darebee.com, in this case maybe on the workout-page, so I precisely mean:
https://darebee.com/wods.html
I want to show just one div class which shows the content of the page, in this case:
<div class="ja-products-wrapper products wrapper grid products-grid cols-3">
That is my code until now (working, but showing full page):
override func viewDidLoad() {
super.viewDidLoad()
let url = URL (string: "https://darebee.com/wods.html")
let request = URLRequest(url: url!)
webview.loadRequest(request)
}
I know there is also a way to hide some parts of the webpage, but it would be far more easier, elegant and shorter to just show this one div-class.
Is it possible to do this? It would be nice to get an answer which is helpful.
UPDATE:
I found a solution that might be helpful for me, but somehow it is not working.
Because the code was 2,5 years old, something I deprecated so I had to update the code to my own.
Maybe on the constraints part, there could be a mistake, but there are no errors at all. Also the backgroundColor is not visible although all the lines I wanted to print are printed.
Maybe you can look to my code and, if it helps you, compare it to the solution I found: How to only display part of web page content (swift)
This is my code:
import UIKit
import JavaScriptCore
import WebKit
class ViewController: UIViewController {
private weak var webView: WKWebView!
#IBOutlet weak var vclabel: UIView!
private var userContentController: WKUserContentController!
override func viewDidLoad() {
super.viewDidLoad()
createViews()
print("no prob1")
loadPage(urlString: "https://dribbble.com/", partialContentQuerySelector: ".dribbbles.group")
print("no prob2")
}
private func createViews() {
userContentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.translatesAutoresizingMaskIntoConstraints = false
let color1 = hexStringToUIColor(hex: "#FFFFFF")
webView.backgroundColor = color1
if (webView.backgroundColor == color1){
print("color1")
}
let leadingConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: topLayoutGuide, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: vclabel, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
view.addSubview(webView)
NSLayoutConstraint.activate([leadingConstraint, topConstraint, trailingConstraint, bottomConstraint])
self.webView = webView
}
private func loadPage(urlString: String, partialContentQuerySelector selector: String) {
userContentController.removeAllUserScripts()
print("load")
let userScript = WKUserScript(source: scriptWithDOMSelector(selector: selector),
injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
forMainFrameOnly: true)
userContentController.addUserScript(userScript)
let url = NSURL(string: urlString)!
webView.load(NSURLRequest(url: url as URL) as URLRequest)
print("loaded")
}
private func scriptWithDOMSelector(selector: String) -> String {
print("DOMSelector")
let script =
"var selectedElement = document.querySelector('\(selector)');" +
"document.body.innerHTML = selectedElement.innerHTML;"
return script
}
func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.characters.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
}
And this is my output:
Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
color1
no prob1
load
DOMSelector
loaded
no prob2
I just don't know what else I should do to get an answer to this question.
Anyone out there who can help me in any way?
Use LoadHtmlString like this:
let requestURL: URL? = yourWebView.request().url
var error: Error?
let page = try? String(contentsOf: requestURL, encoding: String.Encoding.ascii)
yourWebView.loadHTMLString(page!, baseURL: requestURL)
Related
By default, in MacOS, you can double click the titlebar of a window to zoom it (resize to fit the screen - different from maximize). Double clicking it again brings it back to the previous size.
This works fine on my Catalyst app too. However I need to hide the titlebar and need to give my own custom UIView in that titlebar area the double click behavior. I am able to hide it using this:
#if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
UIApplication.shared.connectedScenes.forEach({
if let titlebar = ($0 as? UIWindowScene)?.titlebar {
titlebar.titleVisibility = .hidden
titlebar.toolbar = nil
}
})
#endif
Is there a method which lets me toggle the window zoom?
I was able to figure this out using a workaround. UIKit/Catalyst itself doesn't provide any way to do this. But I was able to use the second method outline in this post on
How to Access the AppKit API from Mac Catalyst Apps
https://betterprogramming.pub/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5
I used the second method and not the first one as the first one seems to be private API (I could be wrong) and will get rejected in App Store. The second method of using a plugin bundle and calling methods on that works well for me. This way I was able to not just perform the zoom, I was also able to perform other MacOS Appkit functionality like listening for keyboard, mouse scroll, hover detection etc.
After creating the plugin bundle, here's my code inside the plugin:
Plugin.swift:
import Foundation
#objc(Plugin)
protocol Plugin: NSObjectProtocol {
init()
func toggleZoom()
func macOSStartupStuff()
}
MacPlugin.swift:
import AppKit
class MacPlugin: NSObject, Plugin {
required override init() {}
func macOSStartupStuff() {
NSApplication.shared.windows.forEach({
$0.titlebarAppearsTransparent = true
$0.titleVisibility = .hidden
$0.backgroundColor = .clear
($0.contentView?.superview?.allSubviews.first(where: { String(describing: type(of: $0)).hasSuffix("TitlebarDecorationView") }))?.alphaValue = 0
})
}
func toggleZoom(){
NSApplication.shared.windows.forEach({
$0.performZoom(nil)
})
}
}
extension NSView {
var allSubviews: [NSView] {
return subviews.flatMap { [$0] + $0.allSubviews }
}
}
Then I call this from my iOS app code. This adds a transparent view at the top where double clicking calls the plugin code for toggling zoom.
NOTE that you must call this from viewDidAppear or somewhere when the windows have been initialized and presented. Otherwise it won't work.
#if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
#objc func zoomTapped(){
plugin?.toggleZoom()
}
var pluginWasLoaded = false
lazy var plugin : Plugin? = {
pluginWasLoaded = true
if let window = (UIApplication.shared.delegate as? AppDelegate)?.window {
let transparentTitleBarForDoubleClick = UIView(frame: .zero)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(zoomTapped))
tapGesture.numberOfTapsRequired = 2
transparentTitleBarForDoubleClick.addGestureRecognizer(tapGesture)
transparentTitleBarForDoubleClick.isUserInteractionEnabled = true
transparentTitleBarForDoubleClick.backgroundColor = .clear
transparentTitleBarForDoubleClick.translatesAutoresizingMaskIntoConstraints = false
window.addSubview(transparentTitleBarForDoubleClick)
window.bringSubviewToFront(transparentTitleBarForDoubleClick)
window.addConstraints([
NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .top, relatedBy: .equal, toItem: window, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0),
transparentTitleBarForDoubleClick.bottomAnchor.constraint(equalTo: window.safeTopAnchor)
])
window.layoutIfNeeded()
}
guard let bundleURL = Bundle.main.builtInPlugInsURL?.appendingPathComponent("MacPlugin.bundle") else { return nil }
guard let bundle = Bundle(url: bundleURL) else { return nil }
guard let pluginClass = bundle.classNamed("MacPlugin.MacPlugin") as? Plugin.Type else { return nil }
return pluginClass.init()
}()
#endif
Calling it from viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
#if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
if !Singleton.shared.pluginWasLoaded {
Singleton.shared.plugin?.macOSStartupStuff()
}
#endif
}
I'm a beginner in Swift programming and would like to add a webView to my app, but I can not set the size of this view properly. The goal was to place this webView in the lower third of the screen (portrait mode), therefore I tried the webView window in the storyboard to look like that and set the constraints.
For programming the webView I oriented myself on the apple dev guide: https://developer.apple.com/documentation/webkit/wkwebview
The Problem is, that every time I launch the app, the webView just appears in fullscreen instead of the modelled area in the storyboard.
I thought maybe I can modify the line with
webView = WKWebView(frame: .zero, configuration: webConfiguration).
I tried to create custom frames, but nothing worked for me, just fullscreen.
Can someone tell me how to setup the webView window with the bounds from the storyboard?
Should I also use the WKWindowFeatures class?
I really searched for hours and watched a lot of youtube-videos for this, nothing helped me. I really appreciate any help, thank you.
UPDATE/Solution:
Thanks to everyone in the comments! Everyone helped me a lot! I tired out the different solutions, some worked from the beginning, others with some further research. I was running into beginner errors like Couldn’t instantiate class named WKWebView or This Class is not Key Value Coding-Compliant for the Key. The first one I solved with checking the constraints and the second one with adding the WebKit Framework in the Build phase Tab in General.
The solution from #AshishGupta worked for me the best.
solutionscreenshot
You can use the following code for simple webView: The image will show the small webView.
import UIKit
import WebKit
class ViewController: UIViewController {
#IBOutlet weak var wkwebView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let myURL = URL(string:"https://www.apple.com")
let myRequest = URLRequest(url: myURL!)
wkwebView.load(myRequest)
}
}
First off, welcome to StackOverflow and iOS.
You're not wrong in your approaches. Both setting constraints right and setting custom frames should work out fine. However there is very minute detail in the Apple developer documentation that could be messing with everything.
There is this line view = webView at the end of the function override func loadView().
This discards everything that has been done to the UIViewController's View property till that point, and sets it to the webView object.
Removing that line should fix all of your problems. If it doesn't, please update your question with a screenshot of your storyboard constraints for the webView.
You should also move your webview set up outside the loadView() function. Probably to viewDidLoad() or to viewWillAppear.
Cheers!
I needed to add a WKWebView as a subview to make it resizeable.
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: CGRect(x: 0, y: self.view.frame.height-500, width: self.view.frame.width, height: 500), configuration: WKWebViewConfiguration())
self.view.addSubview(webView)
let myURL = URL(string:"https://developer.apple.com/")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
That code makes something like this:
Hope this helps!
1. WKWebView Using Constraints
Assuming that you want fix height to the webView, You can set constraint programatically like this
simply take WebKit View from storyBoard and create outlet of ur WebView
#IBOutlet weak var webView: WKWebView!
then set constraint to webView like this
override func viewDidLoad() {
super.viewDidLoad()
webView!.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1, constant: 30)
let trailingConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1, constant: -30)
let bottomConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: -30)
let heightConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 350)
view.addConstraints([trailingConstraint, leadingConstraint, bottomConstraint, heightConstraint])
//Leading = 30 from superview
//Trailing = 30 from superview
//Bottom = 30 from superview
//Equal height = 350
let myURL = URL(string: "https://www.apple.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
2. WKWebView Without Constraints
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView(frame: CGRect(x: 30, y: UIScreen.main.bounds.height-330, width: self.view.frame.width-60, height: 300))
let myURL = URL(string: "https://www.apple.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
view.addSubview(webView)
}
Can someone tell me what am I doing wrong?
I have a tableCell and inside it I have a stackView and inside it a UIWebview added programatically.
class ChatTableViewCell: UITableViewCell {
#IBOutlet var stackView: UIStackView!
func setupCell(message: ChatMessage) {
addHtmlToView(msg: "<p>This is <b>bold text</b></p>")
}
func addHtmlToView(msg: String) {
let webView = UIWebView()
webView.loadHTMLString("<html><body><div id='mainHtml'>" + msg + "</div></body></html>", baseURL: nil)
webView.backgroundColor = UIColor.clear
webView.isOpaque = false
webView.delegate = self
webView.scrollView.isScrollEnabled = false
webView.scrollView.bounces = false
webView.backgroundColor = .red
webView.scrollView.contentInset = UIEdgeInsets(top: -8, left: -8, bottom: -8, right: -8)
webView.translatesAutoresizingMaskIntoConstraints = false
self.stackView.addArrangedSubview(webView)
heightWebViewConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 10)
widthWebViewConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 1)
webView.addConstraint(heightWebViewConstraint)
webView.addConstraint(widthWebViewConstraint)
NSLayoutConstraint.activate([ heightWebViewConstraint, widthWebViewConstraint])
}
}
extension ChatTableViewCell: UIWebViewDelegate {
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
if navigationType == UIWebView.NavigationType.linkClicked {
UIApplication.shared.openURL(request.url!)
return false
}
return true
}
func webViewDidFinishLoad(_ webView: UIWebView) {
webView.frame.size.height = 1.0
webView.sizeToFit()
let result = NumberFormatter().number(from: webView.stringByEvaluatingJavaScript(from: "document.getElementById('mainHtml').offsetHeight") ?? "0")
heightWebViewConstraint.constant = 200
}
}
After this the cell render like this
Next I will use the value from "result" but this doesn't work with an hardcoded value either
I think you have to Add Distribution property of Stack view as "Fill Equally"
I am new to IOS development. Please help me with issue. I am not getting data on Y axis. Fetching data through Alamofire using webAPI. Data is coming nil. Same code is working fine with UIViewController table. I am getting the response. But when using in Graph it is not working.
Issue: I am using Alamofire to parse WebAPI. 'SLPercent' is the value which i want to display on Y-axis.But xml value coming as nil.
class ViewController: UIViewController, ScrollableGraphViewDataSource {
var xml = try! XML.parse("")
var graphView: ScrollableGraphView!
var currentGraphType = GraphType.bar
var graphConstraints = [NSLayoutConstraint]()
var label = UILabel()
var reloadLabel = UILabel()
// Data for the different plots
var numberOfDataItems = day
// Data for graphs with a single plot
/*lazy var simpleLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
*/
lazy var barPlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
// Data for graphs with multiple plots
lazy var blueLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 50)
// lazy var orangeLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 40, shouldIncludeOutliers: false)
// Init
override func viewDidLoad() {
super.viewDidLoad()
let user = "ndbd#gmail.com"
let passwort = "xdc"
var url = URL(string: "https://ceef")
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
configuration.timeoutIntervalForResource = 10
let credentialData = "\(user):\(passwort)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Accept": "application/xml","Authorization": "Basic \(base64Credentials)"]
DispatchQueue.main.async {
Alamofire.request(
url!,
method: .get,
parameters: nil,
encoding: URLEncoding.default,
headers:headers)
.responseString
{ response in
debugPrint(response)
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if response.result.value != nil
{
self.xml = try! XML.parse(response.result.value!)
}
}
}
// self.CallWebAPI()
// Labels for the x-axis
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "⬅", style: .plain, target: self, action: #selector(backAction))
}
func backAction()
{
//print("Back Button Clicked")
dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewDidLoad()
// self.CallWebAPI()
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidLoad()
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
// Implementation for ScrollableGraphViewDataSource protocol
// #########################################################
// You would usually only have a couple of cases here, one for each
// plot you want to display on the graph. However as this is showing
// off many graphs with different plots, we are using one big switch
// statement.
func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double {
switch("bar") {
// Data for the graphs with a single plot
case "bar":
return barPlotData[pointIndex]
default:
return 30
}
}
func label(atIndex pointIndex: Int) -> String {
// Ensure that you have a label to return for the index
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
return xAxisLabels[pointIndex]
}
func numberOfPoints() -> Int {
return numberOfDataItems!
}
// Creating Different Kinds of Graphs
// min: 0
// max: 100
// Will not adapt min and max reference lines to range of visible points
private func createBarGraph(_ frame: CGRect) -> ScrollableGraphView {
let graphView = ScrollableGraphView(frame: frame, dataSource: self)
graphView.topMargin = 200
graphView.bottomMargin = 20
// Setup the plot
let barPlot = BarPlot(identifier: "bar")
barPlot.barWidth = 25
barPlot.barLineWidth = 1
barPlot.barLineColor = UIColor.colorFromHex(hexString: "#777777")
barPlot.barColor = UIColor.colorFromHex(hexString: "#555555")
barPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
barPlot.animationDuration = 1.5
// Setup the reference lines
let referenceLines = ReferenceLines()
referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8)
referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2)
referenceLines.referenceLineLabelColor = UIColor.white
referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(0.5)
// Setup the graph
graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333")
graphView.shouldAnimateOnStartup = true
graphView.rangeMax = 100
graphView.rangeMin = 0
// Add everything
graphView.addPlot(plot: barPlot)
graphView.addReferenceLines(referenceLines: referenceLines)
return graphView
}
fileprivate func createMultiPlotGraphOne(_ frame: CGRect) -> ScrollableGraphView {
let graphView = ScrollableGraphView(frame: frame, dataSource: self)
graphView.topMargin = 200
graphView.bottomMargin = 20
// Setup the first plot.
let blueLinePlot = LinePlot(identifier: "multiBlue")
blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc")
blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
// dots on the line
let blueDotPlot = DotPlot(identifier: "multiBlueDot")
blueDotPlot.dataPointType = ScrollableGraphViewDataPointType.circle
blueDotPlot.dataPointSize = 5
blueDotPlot.dataPointFillColor = UIColor.colorFromHex(hexString: "#16aafc")
blueDotPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
// Setup the reference lines.
let referenceLines = ReferenceLines()
referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8)
referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2)
referenceLines.referenceLineLabelColor = UIColor.white
referenceLines.relativePositions = [0, 0.2, 0.4, 0.6, 0.8, 1]
referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(1)
// Setup the graph
graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333")
graphView.dataPointSpacing = 80
graphView.shouldAnimateOnStartup = true
graphView.shouldAdaptRange = true
graphView.shouldRangeAlwaysStartAtZero = true
// Add everything to the graph.
graphView.addReferenceLines(referenceLines: referenceLines)
graphView.addPlot(plot: blueLinePlot)
graphView.addPlot(plot: blueDotPlot)
return graphView
}
// Constraints and Helper Functions
// ################################
private func setupConstraints() {
self.graphView.translatesAutoresizingMaskIntoConstraints = false
graphConstraints.removeAll()
let topConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.right, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 0)
graphConstraints.append(topConstraint)
graphConstraints.append(bottomConstraint)
graphConstraints.append(leftConstraint)
graphConstraints.append(rightConstraint)
self.view.addConstraints(graphConstraints)
}
// Adding and updating the graph switching label in the top right corner of the screen.
private func addLabel(withText text: String) {
label.removeFromSuperview()
label = createLabel(withText: text)
label.isUserInteractionEnabled = true
let rightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.right, multiplier: 1, constant: -20)
let topConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 80)
let heightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)
let widthConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: label.frame.width * 1.5)
let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(didTap))
label.addGestureRecognizer(tapGestureRecogniser)
self.view.insertSubview(label, aboveSubview: reloadLabel)
self.view.addConstraints([rightConstraint, topConstraint, heightConstraint, widthConstraint])
}
private func addReloadLabel(withText text: String) {
reloadLabel.removeFromSuperview()
reloadLabel = createLabel(withText: text)
reloadLabel.isUserInteractionEnabled = true
let leftConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 20)
let topConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 80)
let heightConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)
let widthConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: reloadLabel.frame.width * 1.5)
let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(reloadDidTap))
reloadLabel.addGestureRecognizer(tapGestureRecogniser)
self.view.insertSubview(reloadLabel, aboveSubview: graphView)
self.view.addConstraints([leftConstraint, topConstraint, heightConstraint, widthConstraint])
}
private func createLabel(withText text: String) -> UILabel {
let label = UILabel()
label.backgroundColor = UIColor.black.withAlphaComponent(0.5)
label.text = text
label.textColor = UIColor.white
label.textAlignment = NSTextAlignment.center
label.font = UIFont.boldSystemFont(ofSize: 14)
label.layer.cornerRadius = 2
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
return label
}
// Button tap events
func didTap(_ gesture: UITapGestureRecognizer) {
currentGraphType.next()
self.view.removeConstraints(graphConstraints)
graphView.removeFromSuperview()
switch(currentGraphType) {
case .bar:
graphView = createBarGraph(self.view.frame)
addReloadLabel(withText: "RELOAD")
addLabel(withText: "BAR")
}
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
func reloadDidTap(_ gesture: UITapGestureRecognizer) {
// TODO: Currently changing the number of data items is not supported.
// It is only possible to change the the actual values of the data before reloading.
// numberOfDataItems = 30
// data for graphs with a single plot
barPlotData = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
blueLinePlotData = self.generateRandomData(self.numberOfDataItems!, max: 50)
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
xAxisLabels = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth)
graphView.reload()
}
// Data Generation
private func generateRandomData(_ numberOfItems: Int, max: Double, shouldIncludeOutliers: Bool = true) -> [Double] {
var data = [Double]()
var counter = 1
for _ in 0 ..< numberOfItems
{
for Result in xml["WebAPiResponse","Result"]
{
let SLPercent = Result["SLPercent"].text!;
let Date = Result["DateCST"].text!;
let DateFromService = Int(Date.substring(to:Date.index(Date.startIndex, offsetBy: 2)))
if (counter == DateFromService!)
{
data.append(Double(SLPercent)!)
}
}
data.append(Double(counter))
counter = counter + 1;
}
return data
}
private func generateRandomData(_ numberOfItems: Int, variance: Double, from: Double) -> [Double] {
var data = [Double]()
for _ in 0 ..< numberOfItems {
let randomVariance = Double(arc4random()).truncatingRemainder(dividingBy: variance)
var randomNumber = from
if(arc4random() % 100 < 50) {
randomNumber += randomVariance
}
else {
randomNumber -= randomVariance
}
data.append(randomNumber)
}
return data
}
private func generateSequentialLabels(_ numberOfItems: Int, text: String) -> [String] {
var labels = [String]()
for i in 0 ..< numberOfItems {
labels.append("\(text) \(i+1)")
}
return labels
}
// The type of the current graph we are showing.
enum GraphType {
case bar
mutating func next() {
switch(self) {
case .bar:
self = GraphType.bar
}
}
}
override var prefersStatusBarHidden : Bool {
return true
}
}
I would like to add a header to my tableView. This header contains 1 UILabel. The header height should be calculated based on the number of lines the label has.
In my code, I'm adding constraints with all the edges of the label <> header. This is my attempt:
//Add header to tableView
header = UIView()
header.backgroundColor = UIColor.yellowColor()
tableView!.tableHeaderView = header
//Create Label and add it to the header
postBody = UILabel()
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
//Enable constraints for each item
postBody.translatesAutoresizingMaskIntoConstraints = false
header.translatesAutoresizingMaskIntoConstraints = false
//Add constraints to the header and post body
let postBodyLeadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
postBodyLeadingConstraint.active = true
let postBodyTrailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
postBodyTrailingConstraint.active = true
let postBodyTopConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
postBodyTopConstraint.active = true
let postBodyBottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
postBodyBottomConstraint.active = true
//Calculate header size
let size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
var frame = header.frame
frame.size.height = size.height
header.frame = frame
tableView!.tableHeaderView = header
header.layoutIfNeeded()
This is my table:
let nib = UINib(nibName: "MessagesTableViewCell", bundle: nil)
let nibSimple = UINib(nibName: "SimpleMessagesTableViewCell", bundle: nil)
self.tableView!.registerNib(nib, forCellReuseIdentifier: "MessagesTableViewCell")
self.tableView!.registerNib(nibSimple, forCellReuseIdentifier: "SimpleMessagesTableViewCell")
self.tableView!.dataSource = self
self.tableView!.delegate = self
self.tableView!.rowHeight = UITableViewAutomaticDimension
self.tableView!.estimatedRowHeight = 100.0
self.tableView!.separatorStyle = UITableViewCellSeparatorStyle.None
self.tableView!.separatorColor = UIColor(hex: 0xf5f5f5)
self.tableView!.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0)
self.tableView!.clipsToBounds = true
self.tableView!.allowsSelection = false
self.tableView!.allowsMultipleSelection = false
self.tableView!.keyboardDismissMode = .OnDrag
As you can see, the header does not take into account the height of the label (which I did numberOfLines = 0)
UILabels take advantage of UIView's intrinsicContentSize() to tell auto layout what size they should be. For a multiline label, however, the intrinsic content size is ambiguous; the table doesn't know if it should be short and wide, tall and narrow, or anything in between.
To combat this, UILabel has a property called preferredMaxLayoutWidth. Setting this tells a multiline label that it should be at most this wide, and allows intrinsicContentSize() to figure out and return an appropriate height to match. By not setting the preferredMaxLayoutWidth in your example, the label leaves its width unbounded and therefore calculates the height for a long, single line of text.
The only complication with preferredMaxLayoutWidth is that you typically don't know what width you want the label to be until auto layout has calculated one for you. For that reason, the place to set it in a view controller subclass (which it looks like your code sample might be from) is in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
postBody.preferredMaxLayoutWidth = CGRectGetWidth(postBody.frame)
// then update the table header view
if let header = tableView?.tableHeaderView {
header.frame.size.height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
tableView?.tableHeaderView = header
}
}
Obviously, you'll need to add a property for the postBody label for this to work.
Let me know if you're not in a UIViewController subclass here and I'll edit my answer.
Implementation using the storyboard
In UItableView add on UITableViewCell new UIView and put him UILabel
Connects them via Autolayout
In UILabel put the number of lines to 0.
In ViewDidLoad your UILabel call a method sizeToFit()
and specify a size for UIView, and that will be your HeaderVew headerView.frame.size.height = headerLabel.frame.size.height
Code
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var headerView: UIView!
#IBOutlet weak var headerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
headerLabel.text = "tableViewdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarning"
headerLabel.sizeToFit()
headerView.frame.size.height = headerLabel.frame.size.height
}
ScreenShot
TestProject
test project link
The first problem we have is that the header cannot be resized by autolayout, for details, see Is it possible to use AutoLayout with UITableView's tableHeaderView?
Therefore, we have to calculate the height of the header manually, for example:
#IBOutlet var table: UITableView!
var header: UIView?
var postBody: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
let header = UIView()
// don't forget to set this
header.translatesAutoresizingMaskIntoConstraints = true
header.backgroundColor = UIColor.yellowColor()
let postBody = UILabel()
postBody.translatesAutoresizingMaskIntoConstraints = false
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont.systemFontOfSize(16.0)
// don't forget to set this
postBody.lineBreakMode = .ByWordWrapping
postBody.numberOfLines = 0
header.addSubview(postBody)
let leadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
header.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
self.table.tableHeaderView = header
self.header = header
self.postBody = postBody
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = postBody!.attributedText!
let height = text.boundingRectWithSize(
CGSizeMake(table.bounds.size.width, CGFloat.max),
options: [.UsesLineFragmentOrigin],
context: nil
).height
header!.frame.size.height = height
}
You might also want to use the code in stefandouganhyde's answer. It does not really matter how you calculate the height. The point is that autolayout won't work automatically for tableHeaderView.
Result:
We use NSLayoutManager to quickly estimate the height for items that need to resize based on the text. This is the basic idea:
override class func estimatedHeightForItem(text: String, atWidth width: CGFloat) -> CGFloat {
let storage = NSTextStorage(string: text!)
let container = NSTextContainer(size: CGSize(width: width, height: CGFloat.max))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
storage.addLayoutManager(layoutManager)
storage.addAttribute(NSFontAttributeName, value: UIFont.Body, range: NSRange(location: 0, length: storage.length))
container.lineFragmentPadding = 0.0
return layoutManager.usedRectForTextContainer(container).size.height
}
Beslan's answer is probably a better fit for your use case, but I find it nice to have more control how the layout is handled.
//may be it will help for you.
header = UIView(frame: CGRectMake(tableview.frame.origin.x,tableview.frame.origin.y, tableview.frame.size.width, 40))
header.backgroundColor = UIColor.yellowColor()
//Create Label and add it to the header
postBody = UILabel(frame: header.frame)
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
let maximumLabelSize: CGSize = CGSizeMake(postBody.size.width, CGFloat.max);
let options: NSStringDrawingOptions = NSStringDrawingOptions.UsesLineFragmentOrigin
let context: NSStringDrawingContext = NSStringDrawingContext()
context.minimumScaleFactor = 0.8
let attr: Dictionary = [NSFontAttributeName: postBody.font!]
var size: CGSize? = postBody.text?.boundingRectWithSize(maximumLabelSize, options:options, attributes: attr, context: context).size
let frame = header.frame
frame.size.height = size?.height
header.frame = frame
postBody.frame = frame
tableView!.tableHeaderView = header
you can calculate the height of a label by using its string
let labelWidth = label.frame.width
let maxLabelSize = CGSize(width: labelWidth, height: CGFloat.max)
let actualLabelSize = label.text!.boundingRectWithSize(maxLabelSize, options: [.UsesLineFragmentOrigin], attributes: [NSFontAttributeName: label.font], context: nil)
let labelHeight = actualLabelSize.height