CALayerInvalidGeometry: CALayer position contains NaN when trying to size a small, remote svg with variable size and aspect ratio - svg

What I want to achieve:
Display small, variable sized svgs in my app with auto layout.
The svgs contain pre-rendered latex formulas. The svgs are loaded asynchronously so I don't know upfront which size or aspect ratio they are.
The hardest pre-conditions I could imagine.
I tried several svg displaying libraries, and no one turned out to work with our svgs, so I decided to use a UIWebView.
The problem here is, that the svgs should not be scrollable and the UIWebView of minimal size.
The svgs should span all available width and have a height based on the aspect ratio.
What I tried:
class FormulaView: UIView, UIWebViewDelegate, XMLParserDelegate {
private var view: UIView!
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var topMargin: NSLayoutConstraint!
#IBOutlet weak var bottomMargin: NSLayoutConstraint!
private var width = -1.0
private var height = -1.0
// MARK: Initialisation
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpView()
}
// MARK: Set Up
// Performs the initial setup.
// Loads a XIB file into a view and returns this view.
func loadSVG(fromURL url: URL) {
let request = NSURLRequest(url: url)
webView.delegate = self
webView.loadRequest(request as URLRequest)
}
func loadSVG(fromString string: String) {
let url = URL(string: string)
if url == nil {
self.isHidden = true
return
}
self.loadSVG(fromURL: url!)
}
var tableView: UITableView?
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
private func setUpView() {
view = viewFromNibForClass()
view.frame = bounds
// Auto-layout stuff.
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
// Formula should take no space if no formula is loaded
topMargin.constant = 0
bottomMargin.constant = 0
self.translatesAutoresizingMaskIntoConstraints = false
self.addConstraints([
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal,
toItem: view, attribute: .right, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal,
toItem: view, attribute: .bottom, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal,
toItem: view, attribute: .left, multiplier: 1.0, constant: 0)
])
}
// MARK: - UIWebView delegate
func webViewDidFinishLoad(_ webView: UIWebView) {
// Get xml of svg file
let xml = webView.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")
let parser = XMLParser(data: (xml?.data(using: .utf8))!)
parser.delegate = self
parser.parse()
}
// MARK: - XMLParser delegate
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
// Get width and height of svg
if elementName == "svg" {
self.height = Double(attributeDict["height"]?
.replacingOccurrences(of: "pt", with: "") ?? "")
?? -1.0
self.width = Double(attributeDict["width"]?
.replacingOccurrences(of: "pt", with: "") ?? "")
?? -1.0
let aspectRatio = self.width / self.height
tableView?.beginUpdates()
// Calculate height the webview should have based on the width and the normal aspect ratio of the svg
let webviewHeight = self.webView.bounds.size.width / CGFloat(aspectRatio)
if let constraint = (webView.constraints.filter {
$0.firstAttribute == .height && $0.secondAttribute == .notAnAttribute
}.first) {
constraint.constant = webviewHeight
} else {
webView.heightAnchor.constraint(equalToConstant: webviewHeight).isActive = true
}
// Apply a 24 margin as a svg is now loaded
topMargin.constant = 24
bottomMargin.constant = 24
tableView?.endUpdates()
self.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
// Zoom webview appropriately that the svg is completely visible and no scrolling is neccessary or even possible
let scrollView = webView.scrollView
let zoom = webView.bounds.size.width / scrollView.contentSize.width
scrollView.maximumZoomScale = zoom
scrollView.setZoomScale(zoom, animated: true) // Error occurs here
// prevent scrolling
scrollView.isScrollEnabled = false
scrollView.isPagingEnabled = false
scrollView.isUserInteractionEnabled = false
scrollView.isDirectionalLockEnabled = true
}
}
}
This version seems to work fine as I tested it on the simulator,
but in some cases (EDIT: I only see these crashes in our Bug reporting tool), I get an error (CALayerInvalidGeometry: CALayer position contains NaN: [nan nan]) at this line scrollView.setZoomScale(zoom, animated: true).
It seems the calculating the zoom results in CGFloat.nan,
(1) but when does this occur,
(2) how can I prevent this and
(3) if it occurs, how can I calculate the zoom in these cases?
EDIT: Or in other words, why are webView.bounds.size.width and/or scrollView.contentSize.width non positiv values?
If I could reproduce it, I could say more, but unluckily I can't...
Thanks in advance,
Dennis

Related

How to scale multiline text by gesture in SwiftUI

I need to implement multiline text scaling by gesture. I tried to use scaleEffect() but it's become blurry and pixelized (first gif). Then I tried to change font directly from gesture but faced another problem: text starts jumping, changing line breaks and etc (second gif). fixedSize() can't solve this because it's multiline text.
I need to implement scaling like with scaleEffect() but without blurring like with scaling font directly. Here's my code:
struct ContentView: View {
#State var activeTextScale: CGFloat = 1.0
#State var lastScale: CGFloat = 1.0
#State var fontSize: CGFloat = 24
var body: some View {
ZStack {
ZStack {
Text(
"Abcdefghijklmnopqrstuvwxyz Abcdefghijklmnopqrstuvwxyz Abcdefghijklmnopqrstuvwxyz"
)
.foregroundColor(.yellow)
// .scaleEffect(activeTextScale)
.font(.system(size: fontSize * activeTextScale))
.background(
Rectangle()
.foregroundColor(.brown.opacity(0.6))
// .scaleEffect(activeTextScale)
)
.gesture(
MagnificationGesture()
.onChanged { value in
activeTextScale = value + lastScale
}
.onEnded { value in
lastScale = activeTextScale
}
)
}
}
}
}
Here I'm tried to scale font and view accordingly, but it gives the same result as on second gif:
struct ContentView: View {
#State var activeTextScale: CGFloat = 1.0
#State var lastScale: CGFloat = 1.0
#State var fontSize: CGFloat = 24
func getTextFontSize() -> CGFloat {
let size = fontSize * activeTextScale
print(activeTextScale)
print(size.rounded())
return size.rounded()
}
var body: some View {
ZStack {
VStack {
Text(
"Abcdefghijklmnopqrstuvwxyz Abcdefghijklmnopqrstuvwxyz Abcdefghijklmnopqrstuvwxyz"
)
.foregroundColor(.yellow)
.font(.system(size: getTextFontSize()))
.background(
Rectangle()
.foregroundColor(.brown.opacity(0.6))
)
}
.scaleEffect(activeTextScale)
.gesture(
MagnificationGesture()
.onChanged { value in
activeTextScale = value + lastScale
}
.onEnded { value in
lastScale = activeTextScale
}
)
}
}
}
First gif with scaleEffect()
Second gif with font scaling
Third gif with scaling view and font accordingly

UIScrollView horizontally scrolling menu in Sprite Kit

I am trying to make a horizontally scrolling menu in my new Sprite Kit game. With Sprite Kit being fairly new, there aren't many good tutorials for Sprite Kit. Is UIScrollView compatible with Sprite Kit? I have tried a couple of ways such as this:
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
scroll.pagingEnabled = YES;
NSInteger numberOfViews = 3;
for (int i = 0; i < numberOfViews; i++) {
CGFloat xOrigin = i * self.view.frame.size.width;
UIView *awesomeView = [[UIView alloc] initWithFrame:CGRectMake(xOrigin, 0, self.view.frame.size.width, self.view.frame.size.height)];
awesomeView.backgroundColor = [UIColor colorWithRed:0.5/i green:0.5 blue:0.5 alpha:1];
[scroll addSubview:awesomeView];
}
This didn't work. I have never worked with UIScrollView before. Can anyone give a way of making a horizontally scrolling menu using UIScrollView? An example of what I am trying to do is in Bloons TD 5 with their menu.
I know this question is a few months old already, but I was also looking for a scrolling menu slider to select levels in my SpriteKit game. I couldn't find any code already existing and the thought of force fitting the UIScrollView into my SpriteKit game didn't appeal to me. So I wrote my own sliding menu for level selection.
I've simulated all of the motions of the UIScrollView so it looks natural, and can be configured to scroll left - right or up - down.
As a bonus I created a parallax background on the menu which can be easily swapped out with new images for a custom look.
https://github.com/hsilived/ScrollingMenu
The code for the Scrolling menu is very well commented so there shouldn't be to many integration issues.
The github project has a fully working demo with Worlds, Levels and parallax images. If you like it mark my answer as useful so I can gain some reputation :)
You can definitely combine UIKit controls with SpriteKit. From the SKScene where you want to have a UIScrollView add the scroll view to self.view, like this: [self.view addSubview:scroll]. You may want to do this in the -(void)didMoveToView:(SKView*)view method on your SKScene. Remember to remove the scroll view from the SKView once you transition out of the SKScene that is using it. You can do that in the -(void)willMoveFromView:(SKView*)view method. Don't forget to set the contentSize on the UIScrollView.
Note: If you want to have SpriteKit nodes over the UIKit controls then you will need to write your own SKSpriteNode that behaves like a UIScrollView.
Expanding on Note: If your scroll view needs to contain Sprite-kit nodes and not just UIKit views you won't be able to use UIScrollView unless you do something clever as suggested in the answer to this question here
There are many ways to obtain it, through SKCameraNode, using UIKit elements like UIScrollView or other ways..
But I want to show you how to do it simple with SpriteKit only.
Conceptually, you should build a larger SKNode that containt every single voice of your menu where each element have an equal distance from the other.
You can simulate the scrolling with an SKAction that move our node to the center of the nearest visible element.
GameViewController:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = false
view.showsDrawCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
GameScene:
class GameScene: SKScene {
var lastX: CGFloat = 0.0
var moveableArea = SKNode() // the larger SKNode
var worlds: [String]! = ["world1","world2","world3"] // the menu voices
var currentWorld:Int = 0 // contain the current visible menu voice index
var worldsPos = [CGPoint]() // an array of CGPoints to store the menu voices positions
override func didMove(to view: SKView) {
//Make a generic title
let topLabel = SKLabelNode.init(text: "select world")
topLabel.fontSize = 40.0
self.addChild(topLabel)
topLabel.zPosition = 1
topLabel.position = CGPoint(x:frame.width / 2, y:frame.height*0.80)
// Prepare movable area
self.addChild(moveableArea)
moveableArea.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
// Prepare worlds:
for i in 0..<worlds.count {
// add a title for i^ world
let worldLabel = SKLabelNode.init(text: worlds[i])
self.moveableArea.addChild(worldLabel)
worldLabel.position = CGPoint(x:CGFloat(i)*self.frame.width ,y:moveableArea.frame.height*0.60)
// add a sprite for i^ world
let randomRed = CGFloat(drand48())
let randomGreen = CGFloat(drand48())
let randomBlue = CGFloat(drand48())
let randomColor = UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
let worldSprite = SKSpriteNode.init(color: randomColor, size: CGSize.init(width: 100.0, height: 100.0))
worldSprite.name = worlds[i]
self.moveableArea.addChild(worldSprite)
worldSprite.position = CGPoint(x:CGFloat(i)*self.frame.width ,y:0.0)
}
}
func tapNode(node:SKNode,action:SKAction) {
if node.action(forKey: "tap") == nil {
node.run(action, withKey: "tap")
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: self)
let touchedNode = self.atPoint(location)
lastX = location.x
let sequence = SKAction.sequence([SKAction.scale(to: 1.5, duration: 0.2),SKAction.scale(to: 1.0, duration: 0.1)]) // a little action to show the selection of the world
switch touchedNode.name {
case "world1"?:
print("world1 was touched..")
tapNode(node:touchedNode,action:sequence)
case "world2"?:
print("world2 was touched..")
tapNode(node:touchedNode,action:sequence)
case "world3"?:
print("world3 was touched..")
tapNode(node:touchedNode,action:sequence)
default:break
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: self)
let currentX = location.x
let leftLimit:CGFloat = CGFloat(worlds.count-1)
let rightLimit:CGFloat = 1.0
let scrollSpeed:CGFloat = 1.0
let newX = moveableArea.position.x + ((currentX - lastX)*scrollSpeed)
if newX < self.size.width*(-leftLimit) {
moveableArea.position = CGPoint(x:self.size.width*(-leftLimit), y:moveableArea.position.y)
}
else if newX > self.size.width*rightLimit {
moveableArea.position = CGPoint(x:self.size.width*rightLimit, y:moveableArea.position.y)
}
else {
moveableArea.position = CGPoint(x:newX, y:moveableArea.position.y)
}
// detect current visible world
worldsPos = [CGPoint]()
for i in 0..<worlds.count {
let leftLimit = self.size.width-(self.size.width*CGFloat(i))
let rightLimit = self.size.width-(self.size.width*CGFloat(i+1))
if rightLimit ... leftLimit ~= moveableArea.position.x {
currentWorld = i
}
worldsPos.append(CGPoint(x: (rightLimit + (leftLimit - rightLimit)/2), y:moveableArea.position.y))
}
lastX = currentX
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if worldsPos.count>0, moveableArea.action(forKey: "moveAction") == nil {
let moveAction = SKAction.move(to: worldsPos[currentWorld], duration: 0.5)
moveAction.timingMode = .easeInEaseOut
self.moveableArea.run(moveAction, withKey: "moveAction")
}
}
}
As you can see , with touchesBegan we can detect the touched world and select it, with touchesMoved we are able to move the moveable node to the correct position and to detect the nearest visible world and with touchesEnded we can create the smooth scrolling to automatic move to the current nearest visible voice after a touch..
Output:

iOS7 - Change UINavigationBar border color

Is it possible to change the grey border-bottom color of the UINavigationBar in iOS7?
I already tried to remove to border, but this is not working:
[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
Thanks!
You are removing the shadow but not the border, you need to do the following:
[[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
To change the border use an image of 2 pixels width line:
[[UINavigationBar appearance] setShadowImage:[UIImage imageNamed:#"2pxWidthLineImage"]];
Here is a category to change bottom color with height:
[self.navigationController.navigationBar setBottomBorderColor:[UIColor redColor] height:1];
Objective C:
UINavigationBar+Helper.h
#import <UIKit/UIKit.h>
#interface UINavigationBar (Helper)
- (void)setBottomBorderColor:(UIColor *)color height:(CGFloat)height;
#end
UINavigationBar+Helper.m
#import "UINavigationBar+Helper.h"
#implementation UINavigationBar (Helper)
- (void)setBottomBorderColor:(UIColor *)color height:(CGFloat)height {
CGRect bottomBorderRect = CGRectMake(0, CGRectGetHeight(self.frame), CGRectGetWidth(self.frame), height);
UIView *bottomBorder = [[UIView alloc] initWithFrame:bottomBorderRect];
[bottomBorder setBackgroundColor:color];
[self addSubview:bottomBorder];
}
#end
Swift:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) {
let bottomBorderRect = CGRect(x: 0, y: frame.height, width: frame.width, height: height)
let bottomBorderView = UIView(frame: bottomBorderRect)
bottomBorderView.backgroundColor = color
addSubview(bottomBorderView)
}
}
Here is another way:
CALayer *border = [CALayer layer];
border.borderColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"border"]].CGColor;
border.borderWidth = 1;
CALayer *layer = self.navigationController.navigationBar.layer;
border.frame = CGRectMake(0, layer.bounds.size.height, layer.bounds.size.width, 1);
[layer addSublayer:border];
The only way I found to change color is:
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = self.navigationController {
let navigationBar = navigationController.navigationBar
let navigationSeparator = UIView(frame: CGRectMake(0, navigationBar.frame.size.height - 1, navigationBar.frame.size.width, 0.5))
navigationSeparator.backgroundColor = UIColor.redColor() // Here your custom color
navigationSeparator.opaque = true
self.navigationController?.navigationBar.addSubview(navigationSeparator)
}
}
I wrote an extension based on the other answers for easier usage in Swift:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor) {
let navigationSeparator = UIView(frame: CGRectMake(0, self.frame.size.height - 0.5, self.frame.size.width, 0.5))
navigationSeparator.backgroundColor = color
navigationSeparator.opaque = true
navigationSeparator.tag = 123
if let oldView = self.viewWithTag(123) {
oldView.removeFromSuperview()
}
self.addSubview(navigationSeparator)
}
}
You can use this extension with calling the method in a context like that:
self.navigationController?.navigationBar.setBottomBorderColor(UIColor.whiteColor())
I found that pretty useful as I had to deal with that colored-border-problem.
I solved this problem with the use of autolayouts. The solution works on different screen sizes and with orientation change.
extension UINavigationBar {
#IBInspectable var bottomBorderColor: UIColor {
get {
return self.bottomBorderColor;
}
set {
let bottomBorderRect = CGRect.zero;
let bottomBorderView = UIView(frame: bottomBorderRect);
bottomBorderView.backgroundColor = newValue;
addSubview(bottomBorderView);
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false;
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,multiplier: 1, constant: 1));
}
}
}
Based on the answer from #sash I made an extension in Swift using Autolayout, explained right here.
In essence, the other solutions have the following pitfalls:
Cannot add dropshadow if using the UIImage solution
The subview added doesn't resize upon rotation of the view
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) -> UIView {
let bottomBorderView = UIView(frame: CGRectZero)
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false
bottomBorderView.backgroundColor = color
self.addSubview(bottomBorderView)
let views = ["border": bottomBorderView]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[border]|", options: [], metrics: nil, views: views))
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: height))
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: height))
return bottomBorderView
}
}
This let you still add a drop shadow if you need to, and this handles rotation nicely !
If you like simple and hacky solutions like I do, create a view that covers the default border:
UIView *navBarLineView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.navigationController.navigationBar.frame),
CGRectGetWidth(self.navigationController.navigationBar.frame), 1)];
navBarLineView.backgroundColor = [UIColor redColor];
[self.navigationController.navigationBar addSubview:navBarLineView];
budidino solutions works very well. Here it is for Swift:
let navBarLineView = UIView(frame: CGRectMake(0,
CGRectGetHeight((navigationController?.navigationBar.frame)!),
CGRectGetWidth((self.navigationController?.navigationBar.frame)!),
1))
navBarLineView.backgroundColor = UIColor.whiteColor()
navigationController?.navigationBar.addSubview(navBarLineView)
Well, if you want to remove bottom border you set shadow image to empty image
[navigationBar setShadowImage:[UIImage new]];
so if you want to set it to another color just create image with that color, I use a helper function to create image from color below (original source http://jslim.net/blog/2014/05/05/ios-customize-uitabbar-appearance/)
+ (UIImage *)imageFromColor:(UIColor *)color forSize:(CGSize)size
{
return [UIImage imageFromColor:color forSize:size withCornerRadius:0];
}
+ (UIImage *)imageFromColor:(UIColor *)color forSize:(CGSize)size withCornerRadius:(CGFloat)radius
{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Begin a new image that will be the new image with the rounded corners
// (here with the size of an UIImageView)
UIGraphicsBeginImageContext(size);
// Add a clip before drawing anything, in the shape of an rounded rect
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius] addClip];
// Draw your image
[image drawInRect:rect];
// Get the image, here setting the UIImageView image
image = UIGraphicsGetImageFromCurrentImageContext();
// Lets forget about that we were drawing
UIGraphicsEndImageContext();
return image;
}
and in my navbar
[navigationBar setShadowImage:[UIImage imageFromColor:[UIColor redColor] forSize:CGSizeMake(CGRectGetWidth(self.tableView.frame), 1)]];
that's it, It's working for me, hope this help. Please consider changing the accepted answer because its not working and can be confusing
To build on #sash's Swift implementation you can make the border responsive to rotation/trait changes by using constraints:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) {
let bottomBorderView = UIView()
bottomBorderView.backgroundColor = color
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bottomBorderView)
// Add constraints to make the bar always stay at the bottom of the nav bar and change size with rotation/trait changes
let horizontalConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: height)
self.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
}
Here's the method for creating image with clear color:
+ (UIImage*)imageFromColor:(UIColor *)color withSize:(CGSize)sizeImage
{
UIImage *resultImage = nil;
UIGraphicsBeginImageContext(sizeImage);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), color.CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0.0f, 0.0f, sizeImage.width, sizeImage.height));
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
}
Here's it's usage for removing annoying bottom line:
navigationBar.shadowImage = [UIImage imageFromColor:[UIColor clearColor] withSize:CGSizeMake(1.0f, 1.0f)];
Picture 1
you can use Reveal to see the border color is the UIImageView's backgroundColor.
so directly modifying the imageView's backgroundColor or hide it.
the code: i write in #interface QdtTabBarController : UITabBarController
Class backGroundClass = NSClassFromString(#"_UIBarBackground");
for (UIView *view in self.tabBar.subviews) {
if ([view isKindOfClass:backGroundClass]) {
for (UIView *view2 in view.subviews) {
if ([view2 isKindOfClass:[UIImageView class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
view2.backgroundColor = [UIColor redColor];
});
};
};
break;
}
}
Picture 2
I'm using RubyMotion with the RedPotion gem, which includes a StandardAppearance class. This is what I did!
Put this line at the top of your app_delegate.rb, just before the on_load method:
ApplicationStylesheet.new(nil).application_setup
Then, in your application_stylesheet.rb, put this as the last line in the application_setup method:
StandardAppearance.apply app.window
And then this is my StandardAppearance class:
class StandardAppearance
def self.apply(window)
Dispatch.once do
UINavigationBar.appearance.tap do |o|
o.setBackgroundImage(UIImage.alloc.init, forBarMetrics: UIBarMetricsDefault)
o.shadowImage = UIImage.alloc.init
end
end
end
end
This will help you :)
[self.navigationController.navigationBar.layer setBorderWidth:2.0];// Just to make sure its working
[self.navigationController.navigationBar.layer setBorderColor:[[UIColor redColor] CGColor]];

iphone:How to use index number on map pin in MKAnnotation View?

Hello friends,
I am developing a functionality to display index number on map pin and set image on mapview of iPhone so please tell me any link or any idea to develop this functionality.
Thanks in advance.
What is an index number in the context? Is it just a digit that your code is displaying? If so your question is how to display text on a map. Use a map overlay. Same for images. Search for MKMapOverlay and go from there.
I have use this method for index number in annotation view.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
// Better to make this class property
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = MKAnnotationView()
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView.frame = CGRect(x: annotationView.frame.origin.x, y: annotationView.frame.origin.y, width: 80, height: 200)
annotationView.annotation = annotation
annotationView.tag = index
index += 1
let imageViewPin = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
imageViewPin.center = CGPoint(x: annotationView.center.x, y: annotationView.center.y - 11)
imageViewPin.image = UIImage(named: "green_pin")
annotationView.addSubview(imageViewPin)
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
debugPrint(view.tag)
}

Underlining text in UIButton

Can anyone suggest how to underline the title of a UIButton ? I have a UIButton of Custom type, and I want the Title to be underlined, but the Interface Builder does not provide any option to do so.
In Interface Builder when you select the Font Option for a Button, it provides option to select None, Single, Double, Color but none of these provide any changes to the Title on the Button.
Any help appreciated.
To use interface builder to underline, one has to:
Change it to attributed
Highlight the text in the Attributes inspector
Right click, choose Font and then Underline
Video someone else made
https://www.youtube.com/watch?v=5-ZnV3jQd9I
From iOS6 it is now possible to use an NSAttributedString to perform underlining (and anything else attributed strings support) in a much more flexible way:
NSMutableAttributedString *commentString = [[NSMutableAttributedString alloc] initWithString:#"The Quick Brown Fox"];
[commentString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [commentString length])];
[button setAttributedTitle:commentString forState:UIControlStateNormal];
Note: added this as another answer - as its a totally different solution to my previous one.
Edit:
oddly (in iOS8 at least) you have to underline the first character otherwise it doesn't work!
so as a workaround, set the first char underlined with clear colour!
// underline Terms and condidtions
NSMutableAttributedString* tncString = [[NSMutableAttributedString alloc] initWithString:#"View Terms and Conditions"];
// workaround for bug in UIButton - first char needs to be underlined for some reason!
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){0,1}];
[tncString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:NSMakeRange(0, 1)];
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){5,[tncString length] - 5}];
[tncBtn setAttributedTitle:tncString forState:UIControlStateNormal];
UIUnderlinedButton.h
#interface UIUnderlinedButton : UIButton {
}
+ (UIUnderlinedButton*) underlinedButton;
#end
UIUnderlinedButton.m
#implementation UIUnderlinedButton
+ (UIUnderlinedButton*) underlinedButton {
UIUnderlinedButton* button = [[UIUnderlinedButton alloc] init];
return [button autorelease];
}
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
#end
You can do it in the interface builder itself.
Select the attribute inspector
Change the title type from plain to attributed
Set appropriate font size and text alignment
Then select the title text and set the font as underlined
It is very simple with attributed string
Creates a dictionary with set attributes and apply to the attributed string. Then you can set the attributed string as attibutedtitle in uibutton or attributedtext in uilabel.
NSDictionary *attrDict = #{NSFontAttributeName : [UIFont
systemFontOfSize:14.0],NSForegroundColorAttributeName : [UIColor
whiteColor]};
NSMutableAttributedString *title =[[NSMutableAttributedString alloc] initWithString:#"mybutton" attributes: attrDict];
[title addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0,[commentString length])]; [btnRegLater setAttributedTitle:title forState:UIControlStateNormal];
Here is my function, works in Swift 1.2.
func underlineButton(button : UIButton, text: String) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, count(text.utf8)))
button.setAttributedTitle(titleString, forState: .Normal)
}
UPDATE Swift 3.0 extension:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
The Swift 5.0 version that works as of September 2019 in Xcode 10.3:
extension UIButton {
func underlineText() {
guard let title = title(for: .normal) else { return }
let titleString = NSMutableAttributedString(string: title)
titleString.addAttribute(
.underlineStyle,
value: NSUnderlineStyle.single.rawValue,
range: NSRange(location: 0, length: title.count)
)
setAttributedTitle(titleString, for: .normal)
}
}
To use it, set your button title first with button.setTitle("Button Title", for: .normal) and then call button.underlineText() to make that title underlined.
Nick's answer is a great, quick way to do this.
I added support in drawRect for shadows.
Nick's answer doesn't take into account if your button title has a shadow below the text:
But you can move the underline down by the height of the shadow like so:
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGFloat shadowHeight = self.titleLabel.shadowOffset.height;
descender += shadowHeight;
Then you'll get something like this:
For Swift 3 the following extension can be used:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIColor *colr;
// set to same colour as text
if (self.isHighlighted || self.isSelected) {
colr=self.titleLabel.highlightedTextColor;
}
else{
colr= self.titleLabel.textColor;
}
CGContextSetStrokeColorWithColor(contextRef, colr.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
//Override this to change the underline color to highlighted color
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
// [self setNeedsDisplay];
}
Expanding on the answer by #Nick H247, I experienced an issue where firstly the underline was not redrawing when the button resized on rotation; this can be solved by setting your button to redraw like so:
myButton.contentMode = UIViewContentModeRedraw;
This forces the button to redraw when the bounds change.
Secondly, the original code assumed you only had 1 line of text in the button (my button wraps to 2 lines on rotation) and the underline only appears on the last line of text. The drawRect code can be modified to first calculate the number of lines in the button, then put an underline on every line rather than just the bottom, like so:
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
constrainedToSize:self.titleLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGSize labelSizeNoWrap = [self.titleLabel.text sizeWithFont:self.titleLabel.font forWidth:self.titleLabel.frame.size.width lineBreakMode:UILineBreakModeMiddleTruncation ];
int numberOfLines = abs(labelSize.height/labelSizeNoWrap.height);
for(int i = 1; i<=numberOfLines;i++) {
// Original code
// CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender + PADDING);
//
// CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + (labelSizeNoWrap.height*i) + descender + PADDING);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + (labelSizeNoWrap.height*i) + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
Hope this code helps someone else!
In swift
func underlineButton(button : UIButton) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: button.titleLabel!.text!)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, button.titleLabel!.text!.utf16Count))
button.setAttributedTitle(titleString, forState: .Normal)}
You can use this code to add underline with spacing in button.
When I tried to draw an underline from interface builder. It look like below image.
1 - Interface builder reference
And after using below code I achieved the result as I wanted.
2 - using described code
public func setTextUnderline()
{
let dummyButton: UIButton = UIButton.init()
dummyButton.setTitle(self.titleLabel?.text, for: .normal)
dummyButton.titleLabel?.font = self.titleLabel?.font
dummyButton.sizeToFit()
let dummyHeight = dummyButton.frame.size.height + 3
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: (self.frame.size.width - dummyButton.frame.size.width)/2, y: -(self.frame.size.height - dummyHeight), width: dummyButton.frame.size.width, height: 1.0)
bottomLine.backgroundColor = self.titleLabel?.textColor.cgColor
self.layer.addSublayer(bottomLine)
}
How will one handle the case when we keep a button underlined pressed? In that case the button's textcolor changes according to highlighted color but line remains of original color. Let say if button text color in normal state is black then its underline will also have black color. The button's highlighted color is white. Keeping button pressed changes button text color from black to white but underline color remains black.
I believe it's some bug in font editor in XCode. If you using interface builder you have to change title from Plain to Attributed, open TextEdit create underlined text and copy-paste to textbox in XCode
Nick H247's answer but Swift approach:
import UIKit
class UnderlineUIButton: UIButton {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let textRect = self.titleLabel!.frame
var descender = self.titleLabel?.font.descender
var contextRef: CGContextRef = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel?.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender!);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender!);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
func underline(text: String, state: UIControlState = .normal, color:UIColor? = nil) {
var titleString = NSMutableAttributedString(string: text)
if let color = color {
titleString = NSMutableAttributedString(string: text,
attributes: [NSForegroundColorAttributeName: color])
}
let stringRange = NSMakeRange(0, text.characters.count)
titleString.addAttribute(NSUnderlineStyleAttributeName,
value: NSUnderlineStyle.styleSingle.rawValue,
range: stringRange)
self.setAttributedTitle(titleString, for: state)
}
Swift 3 version for #NickH247's answer with custom underline color, linewidth and gap:
import Foundation
class UnderlinedButton: UIButton {
private let underlineColor: UIColor
private let thickness: CGFloat
private let gap: CGFloat
init(underlineColor: UIColor, thickness: CGFloat, gap: CGFloat, frame: CGRect? = nil) {
self.underlineColor = underlineColor
self.thickness = thickness
self.gap = gap
super.init(frame: frame ?? .zero)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let textRect = titleLabel?.frame,
let decender = titleLabel?.font.descender,
let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(underlineColor.cgColor)
context.move(to: CGPoint(x: textRect.origin.x, y: textRect.origin.y + textRect.height + decender + gap))
context.setLineWidth(thickness)
context.addLine(to: CGPoint(x: textRect.origin.x + textRect.width, y: textRect.origin.y + textRect.height + decender + gap))
context.closePath()
context.drawPath(using: .stroke)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources