Xamarin iOS - UITextField padding with left image - xamarin

I'm already setting the LeftView of my UITextField so that I can draw a little icon next to the text.
Password.LeftView = new UIImageView(UIImage.FromBundle("LockAndKey"));
Password.LeftViewMode = UITextFieldViewMode.Always;
This works great but the problem now is that the placeholder and actual text is too close to the image - I'd like to indent over the text.
What do I need to do in Xamarin/iOS for my subclassed UITextField to indent the text with a margin? I know there are various functions I can override but haven't had success with it for the text so far.
Thanks!

You need to extend/subclass the UITextField class and override the method called DrawText . There you add the padding you need.
public override void DrawText(CGRect rect)
{
var padding = new UIEdgeInsets (0, 10, 0, 0);
base.DrawText( padding.InsetRect(rect));
}
This will add a 10 points padding to the left.
To make it more customizable I'd add a property called Padding where basically you can set the padding you need depending on the situation.
* UPDATE #1 *
In the case you want to have the space while typing you can just make the UIImageView that represent your LeftView a little bigger in width and play with the ViewContentMode
Using your example:
var leftView = new UIImageView(UIImage.FromBundle("LockAndKey"));
leftView.Frame = new CGRect (leftView.Frame.X, leftView.Frame.Y, leftView.Frame.Width + 15, leftView.Frame.Height);
leftView.ContentMode = UIViewContentMode.Center;
Password.LeftView = leftView
Password.LeftViewMode = UITextFieldViewMode.Always;
With this configuration you will have 7.5 points on each size of the ImageView. You can use others ContentMode to make it work as you'd like.
**** UPDATE #2 ****
Just found another way.
In your subclass UITextField override these two methods:
public override CGRect TextRect (CGRect forBounds)
{
var padding = new UIEdgeInsets (0, 20, 0, 0);
return base.TextRect (padding.InsetRect(forBounds));
}
public override CGRect EditingRect (CGRect forBounds)
{
return TextRect (forBounds);
}
This one is cleaner and as the first solution you can customize it.

Related

NSView drawing explained

I am trying to create a object that can be dragged and rotated in an NSView and have been successful in doing so using NSBezierPath. I am creating multiple objects and storing them in a class and using NSBezierPath.transform(using: AffineTransform) to modify the path in response to drag and rotation inputs.
This all works fine but I now want to add text to the shape and it seems there are a different set of rules for dealing with text.
I have tried using Core Text by creating a CTFrame but have no idea how to move or rotate this.
Is there a good reason for why the handling of text is so different from NSBezierPath.
And then there is the difference between AffineTransform and CGAffineTransform. The whole thing is pretty confusing and good documentation explaining the difference seems hard to come by.
Below is the code for creating and moving the shape which seems work perfectly. I have no idea how to move the text, ideally without having to recreate it. Is there any way to translate and rotate the CTFrame?
var path: NSBezierPath
var location: NSPoint {
didSet {
// move()
}
}
var angle: CGFloat {
didSet {
let dx = angle - oldValue
rotate(dx)
}
}
func createPath(){
// Create a simple path with a rectangle
self.path = NSBezierPath(rect: NSRect(x: -1*width/2.0, y: -1*height/2.0, width: width, height: height))
let line = NSBezierPath()
line.move(to: NSPoint(x: width/2.0, y:0))
line.line(to: NSPoint(x: width/2.0+leader, y:0))
self.path.append(line)
// Label !!
let rect = NSRect(x: width/2.0, y: 0, width: leader, height: height/2.0)
let attrString = NSAttributedString(string: assortmentLabel, attributes: attributesForLeftText)
self.labelFrame = textFrame(attrString: attrString, rect: rect)
// ??? How to rotate the CTFrame - is this even possible
move()
rotate(angle)
}
func rotate(_ da: CGFloat){
// Move to origin
let loc = AffineTransform(translationByX: -location.x, byY: -location.y)
self.path.transform(using: loc)
let rotation = AffineTransform(rotationByDegrees: da)
self.path.transform(using: rotation)
// Move back
self.path.transform(using: AffineTransform(translationByX: location.x, byY: location.y))
}
func move(){
let loc = AffineTransform(translationByX: location.x, byY: location.y)
self.path.transform(using: loc)
}
func draw(){
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
color.set()
path.stroke()
if isSelected {
path.fill()
}
if let frame = self.labelFrame {
CTFrameDraw(frame, context)
}
}
// -----------------------------------
// Modify the item location
// -----------------------------------
func offsetLocationBy(x: CGFloat, y:CGFloat)
{
location.x=location.x+x
location.y=location.y+y
let loc = AffineTransform(translationByX: x, byY: y)
self.path.transform(using: loc)
}
EDIT:
I have changed things around a bit to now draw the shape at origin 0,0 and to then apply the transformation to CGContext prior to drawing the shape.
This does the job and using CTDrawFrame now works correctly...
Well almost...
On my test app it works perfectly but when I integrated the exact same code to the production app the text appears upside-down and all characters shown on top of each other.
As far as I can tell there is nothing different about the views the drawing is taking place in - uses the same NSView subclass.
Is there something else that could upset the drawing of the text - seems like an isFlipped issue but why would this happen in the one app and not the other.
Everything else seems to draw correctly. Tearing my hair out on this.
After much struggling it seems I got lucky with the test app working at all and needed to set the textMatrix on BOTH apps to ensure things work properly under all conditions.
It also seems one can't create the CTFrame and later just scale it and redraw it - well it didn't work for me so I had to recreate it in the draw() method each time!

Set Background Image of a view with stretch to fit in Xamarin

I'm new with Xamarin. I'm actually trying to set the background image of a view and stretch it.
The image is a 2048x1536 pixels png.
nfloat vpHeight = View.Bounds.Height;
nfloat vpWidth = View.Bounds.Width;
Console.WriteLine(vpWidth);
Console.WriteLine(vpHeight);
The above code will return me 1024x768 (it's a landscape position).
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(img);
imgView.ContentMode = UIViewContentMode.ScaleAspectFit;
var prevPage = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight)
};
prevPage.Add(imgView);
this is the code where I set the background, but the result is just the half of the image in x and y just like the image bellow:
So, how to make the image to adjust to the width and height of the view ?
ty !!
I would create an UIImageView as a background like so:
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(img);
imgView.ContentMode = UIViewContentMode.ScaleAspectFit;
then add this to whatever view you are using.
ContentMode can be used like so:
Update
I would add it to the prevPage like so:
var prevPage = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight)
};
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(new CGRect(0,0,vpWidth,vpHeight));
imgView.Image = img;
imgView.ContentMode = UIViewContentMode.ScaleAspectFit; // or ScaleAspectFill
prevPage.Add(imgView);
Also its worth noting that using the View.Bounds to position the view is bit clunky. I would take a look into Autolayout as you will encounter problems on different devices and orientations. These are some good tutorials on Autolayout they might be native code but you are looking for the relationships of the views rather than the code.
Raywenderlich tutorial
Other Tutorial
Any probs with autolayout just ask another question.
I would recommend you stay away from FromPatternImage unless you are really using a pattern.
For the lowest memory consumption and best UI performance, this is what I do:
1st) Resize your image using an image context to match the size of the view:
UIGraphics.BeginImageContext(View.Frame.Size);
UIImage.FromBundle("bg.jpg").Draw(View.Bounds);
var bgImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
2nd) Display the resized image in a UIImageView and send it to the lowest Z-order:
var uiImageView = new UIImageView(View.Frame);
uiImageView.Image = bgImage;
View.AddSubview(uiImageView);
View.SendSubviewToBack(uiImageView);

ConvertPointToView Function not working in Swift Xcode 7 Beta

So I reverently updated to Xcode 7 Beta and I'm somewhat unsure as to whether that is pertinent information but I figured I'd mention it anyway. My issue is that when I use the convertPointToView() function, it calls for an argument of type SKScene whereas (obviously) I want to it to take a point. I have another class which doesn't inherit SKScene where I want to use this function and it errors with "use of unresolved identifier convertPointToView" (I am importing SKScene). I find this to be very strange since I've written other programs that use this function and work fine even with the Xcode 7 beta, however, it doesn't seem to be working here. If anyone knows why I am having all this trouble, I'd really appreciate some help.
import Foundation
import SpriteKit
import SceneKit
class StandardLabel: UILabel {
init(x: Double, y: Double, width: Double, height: Double, doCenter: Bool, text: String, textColor: UIColor, backgroundColor: UIColor, font: String, fontSize: CGFloat, border: Bool, sceneWidth: Int) {
var frame = CGRect()
if doCenter {
frame = CGRect(x: convertPointToView(CGPoint(x: sceneWidth / 2, y: 0)).x, y: height, width: width, height: height)
} else {
frame = CGRect(x: x, y: y, width: width, height: height)
}
super.init(frame: frame)
self.text = text
self.textColor = textColor
self.backgroundColor = backgroundColor
self.textAlignment = NSTextAlignment.Center
self.font = UIFont(name: font, size: fontSize)
if border {
super.layer.borderColor = UIColor.blackColor().CGColor
super.layer.borderWidth = 5
}
}
I'd suggest that if you want to add text within your scene, that you use SKLabelNode, because it uses the same coordinate system as the other items that you'll be aligning, and you won't have to worry about converting your coordinates between different systems.
Use a UILabel if you want to display text outside of your scene (although for a SpriteKit (2D) game this wouldn't make sense because they're full screen, and I think would make sense mostly if you're making a SceneKit (3D) game).
Also, you don't need to make a subclass just to set default color and text size, it probably makes more sense to have this as a private helper method on your scene.
SKLabelNode doesn't have any 'border' property (like UILabel.layer does) so instead you'll need to draw an outline using SKShapeNode. This example uses the exact frame of the label so the text and the border are touching. You probably want to add a gap so there's space between the text and border but I'll leave that to you.
Here's a commented code snippet that adds two labels to a scene: "Baore" at size 80 with red text and border, and the label "Hello" at size 30 with blue text and border. Both are centered horizontally, "Baore" is also centered vertically, "Hello" is offset from the center by 150.
You could have also moved the position logic into the helper function, but to me that just feels weird... but up to you to decide how you want to do that!
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// `makeLabelWithOutline` is a helper function that takes 3 parameters
// - labelText
// - textSize
// - tint color to use for text and outline
// because textSize and tint have default values, you can
// omit them, and the default values will be used instead
// so this will be a label with text "Baore" at size 80 and red text and outline
let baoreLabel = self.makeLabelWithOutline("Baore")
baoreLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(baoreLabel)
// so this will be a label with text "Hello" at size 30 and blue text and outline
let helloLabel = self.makeLabelWithOutline("hello", textSize: 30, tint: UIColor.blueColor())
helloLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)+150)
self.addChild(helloLabel)
}
private func makeLabelWithOutline(labelText:String, textSize:CGFloat = 80, tint:UIColor = UIColor.redColor()) -> SKNode {
let myLabel = SKLabelNode(fontNamed:"Futura-CondensedExtraBold")
myLabel.text = labelText
myLabel.fontColor = tint
myLabel.fontSize = textSize
// create the outline for the label
let labelOutline = SKShapeNode(rect: myLabel.frame)
labelOutline.strokeColor = tint
labelOutline.lineWidth = 5
labelOutline.addChild(myLabel)
return labelOutline
// we're returning an SKNode with this hierachy:
// SKShapeNode (the outline)
// ↳ SKLabelNode (the "Baore" label)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
convertPointToView is an instance method of an SKScene object so you'll need to call it on some instance of an SKScene (maybe pass in a reference to the entire scene instead of just the sceneWidth?)
I'm not sure if it helps, but there's also convertPoint:toView: which might make more sense if you're dealing with a UILabel (??)

iOS 8 Swift Xcode 6 - Set top nav bar bg color and height

I have looked everywhere and tested all the code snippets posted on Stack, but nothing works for me as I need it to work.
I simply want to set:
Nav bar height
Nav bar bg color in RGB
Nav bar centered logo
I'm working with iOS8, Xcode 6 and Swift.
Many thanks for a clear answer!
This is my code in ViewController.swift
// Set nav bar height
navigationController?.navigationBar.frame.origin.y = -10
// Set nav bar bg color
var navBarColor = UIColor(red: 4 / 255, green: 47 / 255, blue: 66 / 255, alpha: 1)
navigationController?.navigationBar.barTintColor = navBarColor
// Set nav bar logo
let navBarImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
navBarImageView.contentMode = .ScaleAspectFit
let navBarImage = UIImage(named: "navBarLogo.png")
navBarImageView.image = navBarImage
navigationItem.titleView = navBarImageView
After applying the code in the accepted answer, the height doesn't seem to change at all..
It's not an easy job...and I've surveyed several articles online (most of them in Objective-C).
The most useful one is this: http://www.emdentec.com/blog/2014/2/25/hacking-uinavigationbar
But its final solution does not put items in the middle, and it's not in Swift.
So I come up with a workable version in Swift. Hope it helps some people as I was saved so many precious time on SO.
Solution in Swift:
The following code will solve some issues you may have encountered:
The title & items are not placed in the middle of the navigation bar
The title & items would flick when the user navigates between view controllers
You need to subclass the UINavigationBar first, and in your storyboard, select the navigation bar element, and in the "Identity Inspector" tab, set the new class as the Custom Class
import UIKit
class UINavigationBarTaller: UINavigationBar {
///The height you want your navigation bar to be of
static let navigationBarHeight: CGFloat = 64
///The difference between new height and default height
static let heightIncrease:CGFloat = navigationBarHeight - 44
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
let shift = UINavigationBarTaller.heightIncrease/2
///Transform all view to shift upward for [shift] point
self.transform =
CGAffineTransformMakeTranslation(0, -shift)
}
override func layoutSubviews() {
super.layoutSubviews()
let shift = UINavigationBarTaller.heightIncrease/2
///Move the background down for [shift] point
let classNamesToReposition: [String] = ["_UINavigationBarBackground"]
for view: UIView in self.subviews {
if classNamesToReposition.contains(NSStringFromClass(view.dynamicType)) {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 20.0
view.frame = frame
}
}
}
override func sizeThatFits(size: CGSize) -> CGSize {
let amendedSize:CGSize = super.sizeThatFits(size)
let newSize:CGSize = CGSizeMake(amendedSize.width, UINavigationBarTaller.navigationBarHeight);
return newSize;
}
}
Also on my gist: https://gist.github.com/pai911/8fa123d4068b61ad0ff7
iOS 10 Update:
Unfortunately, this code breaks in iOS 10, there is someone who helps fix it, here you go:
iOS 10 custom navigation bar height
And to be clear, this code is kind of hacky since it depends on the navigation bar's internal structure...so if you decide to use it anyway, be prepared for any upcoming changes that may break this code...
Nav bar height:
In a custom navigation controller subclass...
The trick with this one is to NOT change the actual height of the navigation bar and instead adjust its origin.
func viewDidLoad() {
super.viewDidLoad()
navigationBar.frame.origin.y = -10
}
Nav bar bg color in RGB:
In a custom navigation controller subclass...
func viewDidLoad() {
super.viewDidLoad()
navigationBar.barTintColor = // YOUR COLOR
}
or use the appearance proxy
UINavigationBar.appearance().barTintColor = // YOUR COLOR
Nav bar centered logo
In a custom view controller...
func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: // YOUR LOGO)
}
Great answer from Bon Bon!
In Swift 3 however make sure you replace
let classNamesToReposition: [String] = ["_UINavigationBarBackground"]
with
let classNamesToReposition: [ String ] = [ "_UIBarBackground" ]
Otherwise, it wont work.

What am I doing wrong with making the corner radius exposed in Interface Builder?

This is driving me nutty. I added a new framework and added a UIImageView subclass to be part of it. I then created a UITableViewCell, made it an instance of my custom UIImageView and set its exposed corner radius to 5.
In my subclass I just have:
#IBDesignable class MediaPostCellImageView: UIImageView {
#IBInspectable var cornerRadius: CGFloat = 3.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
}
Here's an example project showing it: http://cl.ly/3Z053m1O3C0i
Why is this not showing it in Interface Builder?
You need to set layer.masksToBounds property to true.
Also, if you still can't see the rounded corner, try to change the background colour, just in case it actually has rounded corners but you can't see it because the colours are the same with the superview.
#IBInspectable var cornerRadius: CGFloat {
get { layer.cornerRadius }
set {
layer.cornerRadius = newValue
if newValue > 0 {
layer.masksToBounds = true
}
}
}
You can use something like this in the extension. Once the new value is set for the corner radius and it's greater than 0, layer.maskToBounds is set to true.

Resources