UILabel containing HTML/Attributed Text - how to scale when inside a UIScrollView - swift

I do have a UIScrollView only containing a UILabel. The UILabel contains an NSAttributedString made out of HTML:
let str = try NSAttributedString(data: content.data(using: String.Encoding.unicode, allowLossyConversion: true)!, options: [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType], documentAttributes: nil)
Now I would like to let the user zoom the label to have everything bigger. When only using
func viewForZooming(in scrollView: UIScrollView) -> UIView?
everything works as expected, but the font gets blurry. I tried changing the font size after zooming:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
myLabel.setFontSize(pointSize: scrollView.zoomScale)
}
with the function being the following:
extension UILabel {
func setFontSize(pointSize: CGFloat) {
let fullRange = NSRange(location: 0, length: text!.count)
let mutableAttributeText = NSMutableAttributedString(attributedString: attributedText!)
mutableAttributeText.enumerateAttribute(NSFontAttributeName, in: fullRange, options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired) {
(attribute: Any!, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
if let attributeFont = attribute as? UIFont {
let newPointSize = 12 * pointSize
let scaledFont = UIFont(descriptor: attributeFont.fontDescriptor, size: newPointSize)
mutableAttributeText.addAttribute(NSFontAttributeName, value: scaledFont, range: range)
}
}
attributedText = mutableAttributeText
}
}
But then the text is not positioned exactly the same as before. Now what's the best way to let the user zoom in and out and have the text still sharp? I'm using auto layout to position the label inside UIScrollView.

#swalkner since you have to display a html, why not use a WKWebview instead of UIScrollView.
let htmlString = "some_html"
let webView:WKWebView = WKWebView.init(frame: frame)
webView.backgroundColor = UIColor.white
self.view.addSubview(webView)
// load html
webView.loadHTMLString(htmlString, baseURL: nil)
And if the load html takes a little bit of time, you can simply try to preload the webview, by creating it and load the html before push or present the controller.

Related

Problem to save to pdf file a NSView with rotated NSImageView as subview - NSImageView is not rotated

I have NSView and I am adding programatically a NSImageView (or NSTextField) as a subview. Then I do rotation of this subview by setting the angle for frameCenterRotation. The image of NSImageView is rotated.
myNSImageView.frameCenterRotation = angle
But, when I try to print NSView or save it to pdf file this rotation is not applied to the NSImageView subview. In print preview or in pdf file the image is not rotated.
How to save/print NSView with rotated image exactly as it is on the screen ?
Update. More details. Code for the NSView. For this NSview .wantsLayer = true
NSImage is added from NSURL
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool
{
guard let pasteboardObjects = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: nil), pasteboardObjects.count > 0 else {
return false
}
sender.draggingDestinationWindow?.orderFrontRegardless()
let mousePoint = self.convert(sender.draggingLocation, from: nil)
for each in pasteboardObjects
{
if let url = each as? NSURL
{
if let image = NSImage(contentsOfFile: url.path!)
{
let constrainedSize = image.representations.first!.size
let imageView = NSImageView(frame:NSRect(x: mousePoint.x - constrainedSize.width/2, y: mousePoint.y - constrainedSize.height/2, width: constrainedSize.width, height: constrainedSize.height))
imageView.image = image
arrayOfNSImageViews.append(imageView)
self.addSubview(imageView)
}
}
}
return true
}
NSImageView is rotated by these mouse events:
override func rightMouseDown(with event: NSEvent)
{
mouseDownPointRight = self.convert(event.locationInWindow, from: nil)
for i in (0..<arrayOfNSImageViews.count).reversed()
{
if arrayOfNSImageViews[i].frame.contains(mouseDownPointRight) == true
{
imageViewToRotate = arrayOfNSImageViews[i]
break
}
}
}
override func rightMouseDragged(with event: NSEvent)
{
let draggedMousePoint = self.convert(event.locationInWindow, from: nil)
angle = draggedMousePoint.y - mouseDownPointRight.y / 1000
imageViewToRotate.frameCenterRotation = angle
}
And printing myNSView (in document based app)
override func printOperation(withSettings printSettings: [NSPrintInfo.AttributeKey : Any]) throws -> NSPrintOperation
{
self.printInfo.isHorizontallyCentered = true
self.printInfo.isVerticallyCentered = true
self.printInfo.verticalPagination = .fit
self.printInfo.horizontalPagination = .fit
let printOperation: NSPrintOperation = NSPrintOperation(view: myNSView, printInfo: self.printInfo)
printOperation.showsPrintPanel = true
return printOperation
}

Swift Scrollview scroll so that button is right above keyboard when keyboard appears

I have tried literally every solution on the internet to get this answer, and none of them work without bugs. I have a stackview embedded in a scrollview, and I want the signInButton to be right above the keyboard.
Here is my current attempted solution:
#objc func keyboardWillShow(notification: NSNotification) {
guard let userInfo: NSDictionary = notification.userInfo as NSDictionary?,
let keyboardInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
return
}
let keyboardSize = keyboardInfo.cgRectValue.size
scrollView.isScrollEnabled = false
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var rect = self.view.frame
rect.size.height -= keyboardSize.height
var frame = self.signInButton.frame
frame.size.height += 50
self.scrollView.scrollRectToVisible(frame, animated: true)
}
#objc func keyboardWillHide(notification: NSNotification) {
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInset
scrollView.isScrollEnabled = false
}
But this does not come close to working on all screen sizes. While the button always appears sometimes it appears way above the keyboard, and other times slightly above. And when the keyboard disappears on some screens it doesn't work either. I'm curious if anyone knowledgeable can give a robust answer that many can use for this ubiquitous problem. Swift 5 please.
I know I can use a tableview, but I find it incredibly sad that iOS does not have a clear, alternative working solution without embedding everything in a tableview.
Thanks!
I have had similar problems and i fixed it by using this. (probably not the best way, but it works)
Add these in viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboarddismissedlogin(notification:)), name: Notification.Name("keyboarddismissedlogin"), object: nil)
Define those 3 functions
// MARK: - KeyboardON
#objc func keyboardWillShow(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo! as NSDictionary
let keyboardFrame:NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
TableBottomConstraint.constant = TableBottomConstraint.constant + keyboardHeight
extraFunctions.keyboardHeight = Int(keyboardHeight)
}
// MARK: - keyboardOFFheight
#objc func keyboardWillHide(notification:NSNotification) {
let userInfo:NSDictionary = notification.userInfo! as NSDictionary
let keyboardFrame:NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
if TableBottomConstraint.constant > keyboardHeight
{
TableBottomConstraint.constant = TableBottomConstraint.constant - keyboardHeight
}
}
// MARK: - Keyboard Dismiss
#objc func keyboarddismissedlogin(notification: Notification) {
TableBottomConstraint.constant = TableBottomConstraint.constant - CGFloat(extraFunctions.keyboardHeight)
}
Here you have to make an outlet of your table or scroll view's bottom constraint.
extraFunctions.keyboardHeight
is simply a struct that i created to store the keyboard height. (you can simply create a variable instead of struct - > variable. I did it just for the sake of naming it)
This will simply move your view UP from the bottom when keyboard appears and changes back to original position when keyboard disappears.
All this works , assuming that your BUTTON is at the bottom of your view.
I donot fully get your question. In my case, I put the constraint to the bottom of the button, and set its constant when the keyboard appear/ dissappaear.
#IBOutlet weak var edittingBottomConstraint: NSLayoutConstraint!
#objc func showKeyboard(notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyBoardHeight = keyboardRectangle.size.height
edittingBottomConstraint.constant = -keyBoardHeight
}
}

How to change font of buttons in UIAlertController

I need to change font of buttons in UIAlertController, I've used the code below, but I doesn't work properly, because when the user touches the buttons of UIAlertController, the font will be change to it's default.
extension UIAlertController {
private func changeFont(view:UIView,font:UIFont) {
for item in view.subviews {
if let col = item as? UICollectionView {
for row in col.subviews{
changeFont(view: row, font: font)
}
}
if let label = item as? UILabel {
label.font = font
} else {
changeFont(view: item, font: font)
}
}
}
//To set font for any UILabels in action sheet
open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let font = MyCustomFont
changeFont(view: self.view, font: font! )
}
}
and here is the usage of this extension:
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let action = UIAlertAction(title: STRING_OF_ACTION, style: .default) { _ in
//some action code
}
optionMenu.addAction(action)
//in UIViewController
self.present(optionMenu, animated: true, completion: nil)
It's hard to change it because the iOS system doesn't provide the API for us to do it. So we'd better use third party libraries like: SCLAlertView-Swift or PopupDialog if we need some customization.

How do we make keyboard appear below textView in swift?

I have done keyboard appearing below textfield using
on View did Load adding a observer()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(Gold_Loan_First_ViewController.keyboardWillBeHidden(_:)), name: UIKeyboardWillHideNotification, object: nil)
And then updating the frame
weak var activeField: UITextField?
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField==txtOTP {
txtOTP.errorMessage=""
}
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func keyboardDidShow(notification: NSNotification)
{
if let activeField = self.activeField,
let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= keyboardSize.size.height
if (!CGRectContainsPoint(aRect, activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification)
{
let contentInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
}
But how do I do it for a textView. I tried the same code with didBeginEditing of textView with no positive effect
One of the easy and no code of line solution is to use the following pods in your app.
IQKeyboardManger
Later you need to just import that in App Delegate and add this two lines of code in didfinishLaunching method:
IQKeyboardManager.sharedManager().enable = true
Your problem will be solved for whole app.
For Swift 5:
IQKeyboardManager.shared.enable = true
I have gone through such a situation. For this first I added extension in my UiViewController :
extension CCViewController : UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidEndEditing(_ textView: UITextView) {
// write code as per your requirements
}
func textViewDidChange(_ textView: UITextView) {
self.p_adjustMessageFieldFrameForTextView(textView)
}
}
// Defining the p_adjustMessageFieldFrameForTextView method
fileprivate func p_adjustMessageFieldFrameForTextView(_ textView : UITextView) {
var finalheight : CGFloat = textView.contentSize.height + 10
var kMaxMessageFieldHeight : CGFloat = 50
if (finalheight > kMaxMessageFieldHeight) {
finalheight = kMaxMessageFieldHeight;
} else if (finalheight < kCommentTextViewHeight){
finalheight = kCommentTextViewHeight;
}
scrollView!.view.frame = CGRect(x: 0, y: kNavBarHeight + statuBarHeight, width: SCREEN_WIDTH,height: SCREEN_HEIGHT - finalheight - keyboardRect!.size.height - kNavBarHeight - statuBarHeight)
// It is there for understanding that we have to calculate the exact frame of scroll view
commentTextView!.frame = CGRect(x: 0, y: bottomOfView(scrollView!.view), width: SCREEN_WIDTH, height: finalheight)
self.p_setContentOffsetWhenKeyboardIsVisible()
}
// Defining the p_setContentOffsetWhenKeyboardIsVisible method
fileprivate func p_setContentOffsetWhenKeyboardIsVisible() {
// here you can set the offset of your scroll view
}
Also there is a method named bottomView() :
func bottomOfView(_ view : UIView) -> CGFloat {
return view.frame.origin.y + view.frame.size.height
}
You can simply use TPKAScrollViewController.h & TPKAScrollViewController.m files by using Bridging Header.
While dragging these objective-C file to your swift project, it will automatically ask for Create Bridging. Create Bridging Header and Import #import "TPKAScrollViewController.h" to YourApp-Bridging-Header.h file.
After that, simply select your scrollView in XIB and change its class to TPKeyboardAvoidingScrollView as showing below.
Salman Ghumsani is Right.
Please change UIKeyboardFrameBeginUserInfoKey to UIKeyboardFrameEndUserInfoKey.
Apple Debeloper Document: Keyboard Notification User Info Keys
UIKeyboardFrameEndUserInfoKey
The key for an NSValue object containing a CGRect that identifies the ending frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
UIKeyboardFrameBeginUserInfoKey
The key for an
NSValue
object containing a
CGRect
that identifies the starting frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device.
Conclusion
So, if u use UIKeyboardFrameBeginUserInfoKey u might get the keyboard height to 0. Causing the system thought UITextView was not covered.
Find the correct height of keyboard and assign it to the bottom constraint of the textView or reduce the y position of textView.
Like:
Step-1:
make a property keyboardHeight in you viewController.
var keyboardHeight: CGFloat = 0.0
Step-2: Make #IBOutlet of bottom constraint of the textView.
#IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint!
Step-3:
fileprivate func addKeyboardNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
fileprivate func removeKeyboardNotification() {
IQKeyboardManager.shared().isEnabled = true
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Copy & paste these functions to your view controllers, and call self.addKeyboardNotification() in viewDidLoad() and of you viewController
Step-4:
deinit {
self.removeKeyboardNotification()
}
also add this code to your viewController.
Step-5:
func keyboardWillShow(_ notification: Notification) {
if let keboardFrame = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue, self.keyboardHeight <= 0.0 {
self.keyboardHeight = keboardFrame.height + 45.0 //(Add 45 if your keyboard have toolBar if not then remove it)
}
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = self.keyboardHeight
}, completion: { (success) in
})
}
func keyboardWillHide(_ notification: Notification) {
UIView.animate(withDuration: 0.3, animations: {
self.textViewBottomConstraint.constant = 0.0
}, completion: { (success) in
})
}
That's it to manage the textView with keyboard.
If you don't want to use textViewBottomConstraint the you can work with the y position of the textView.

Change title and message font of alert using UIAlertController in Swift

I am trying to change the title and message font for an alert shown using UIAlertController
I am trying to do using an NSAttributedStirng, but it gives compiler error that a NSAttributed string cannot be taken instead of Stirng
I tried something similar to this
var title_attrs = [NSFontAttributeName : CustomFonts.HELVETICA_NEUE_MEDIUM_16]
var msg_attrs = [NSFontAttributeName : CustomFonts.HELVETICA_NEUE_REGULAR_14]
var title = NSMutableAttributedString(string:"Done", attributes:title_attrs)
var msg = NSMutableAttributedString(string:"The job is done ", attributes:msg_attrs)
let alertController = UIAlertController(title: title, message: title , preferredStyle: UIAlertControllerStyle.Alert)
Can someone guide me how can I achieve this?
Swift 3 Version:
extension UIAlertController {
func changeFont(view: UIView, font:UIFont) {
for item in view.subviews {
if item.isKind(of: UICollectionView.self) {
let col = item as! UICollectionView
for row in col.subviews{
changeFont(view: row, font: font)
}
}
if item.isKind(of: UILabel.self) {
let label = item as! UILabel
label.font = font
}else {
changeFont(view: item, font: font)
}
}
}
open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let font = YOUR_FONT
changeFont(view: self.view, font: font! )
}
}
I think Apple removed the attributedTitle and -message from the API. It was never part of the public API so it might be that Apple will not allow your app in the app store if you used it.
You should use the UIAlertController as is. If you want to customise it a bit see this NSHipster post. If you want more control, create a custom View to display.
let myString = "Alert Title"
var myMutableString = NSMutableAttributedString()
myMutableString = NSMutableAttributedString(string: myString as String, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 18.0)!])
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSRange(location:0,length:myString.characters.count))
alertController.setValue(myMutableString, forKey: "attributedTitle")
alertController.setValue(myMutableString, forKey: "attributedMessage")
extension UIAlertController {
func changeFont(view:UIView,font:UIFont) {
for item in view.subviews {
if item.isKindOfClass(UICollectionView) {
let col = item as! UICollectionView
for row in col.subviews{
changeFont(row, font: font)
}
}
if item.isKindOfClass(UILabel) {
let label = item as! UILabel
label.font = font
}else {
changeFont(item, font: font)
}
}
}
public override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let font = UIFont(name: YourFontName, size: YourFontSize)
changeFont(self.view, font: font! )
}
}