I'm missing something here. I got Liveness to work in Interface Builder. I can draw the image in the NSView with NSImage.draw rect. So the image loads correctly. However when I put this inside a CALayer it doesn't show up.
Did I miss something about behaviour on NSView? CALayer? Layer Hosting? Or something else?
Here's the code of the view:
import Foundation
import AppKit
import QuartzCore
#IBDesignable public class CircularImageView: NSView {
var imageLayer: CALayer?
#IBInspectable public var edgeInset: CGFloat = 10
public var image: NSImage? {
didSet {
if let newImage = image {
imageLayer?.contents = newImage
}
}
}
// MARK: New in this class
private func prepareLayer() {
self.layer = CALayer()
self.wantsLayer = true
}
private func drawImage() {
// What am I doing wrong here?
var newImageLayer = CALayer()
newImageLayer.contentsGravity = kCAGravityResizeAspect
if let imageToSet = image {
newImageLayer.contents = imageToSet
}
let insetBounds = CGRectInset(self.bounds, edgeInset, edgeInset)
newImageLayer.frame = insetBounds
newImageLayer.backgroundColor = NSColor(calibratedWhite: 0.8, alpha: 1).CGColor
self.layer!.addSublayer(newImageLayer)
imageLayer = newImageLayer
}
private func test(){
image?.drawInRect(self.bounds)
}
// MARK: NSView stuff
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
prepareLayer()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
prepareLayer()
}
public override func viewWillDraw() {
super.viewWillDraw()
drawImage()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
// Load default test image.
println("\(self): prepareForInterfaceBuilder")
let processInfo = NSProcessInfo.processInfo()
let environment = processInfo.environment
let projectSourceDirectories : AnyObject = environment["IB_PROJECT_SOURCE_DIRECTORIES"]!
let directories = projectSourceDirectories.componentsSeparatedByString(":")
if directories.count != 0 {
let firstPath = directories[0] as! String
let imagePath = firstPath.stringByAppendingPathComponent("CircularView/Bz1dSvR.jpg")
let image = NSImage(contentsOfFile: imagePath)
image!.setName("Test Image")
self.image = image
}
}
}
Thanks in advance.
Okay, guys I found the answer.
I did two things wrong in this code. I was working on a layer hosting view and I simply needed a layer-backed view. I didn't new there was a difference. And before adding an NSImage to the CALayer's contents I needed to embrace it with lockFocus() and unlockFocus().
Here's the full code that solved the issue.
import Foundation
import AppKit
#IBDesignable public class CircularImageView: NSView {
var imageLayer: CALayer?
#IBInspectable public var edgeInset: CGFloat = 10
public var image: NSImage? {
didSet {
if let newImage = image {
imageLayer?.contents = newImage
}
}
}
// MARK: New in this class
private func prepareLayer() {
// I had to remove my own created layer.
self.wantsLayer = true
}
private func drawImage() {
var newImageLayer = CALayer()
newImageLayer.contentsGravity = kCAGravityResizeAspect
if let imageToSet = image {
// I didn't lock the focus on the imageToSet.
imageToSet.lockFocus()
newImageLayer.contents = imageToSet
// I didn't unlock the focus either.
imageToSet.unlockFocus()
}
let insetBounds = CGRectInset(self.bounds, edgeInset, edgeInset)
newImageLayer.frame = insetBounds
newImageLayer.backgroundColor = NSColor(calibratedWhite: 0.8, alpha: 1).CGColor
self.layer!.addSublayer(newImageLayer)
imageLayer = newImageLayer
}
// MARK: NSView stuff
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
prepareLayer()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
prepareLayer()
}
public override func viewWillDraw() {
super.viewWillDraw()
drawImage()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
// Load default test image.
println("\(self): prepareForInterfaceBuilder")
let processInfo = NSProcessInfo.processInfo()
let environment = processInfo.environment
let projectSourceDirectories : AnyObject = environment["IB_PROJECT_SOURCE_DIRECTORIES"]!
let directories = projectSourceDirectories.componentsSeparatedByString(":")
if directories.count != 0 {
let firstPath = directories[0] as! String
let imagePath = firstPath.stringByAppendingPathComponent("CircularView/Bz1dSvR.jpg")
let image = NSImage(contentsOfFile: imagePath)
image!.setName("Test Image")
self.image = image
}
}
}
Related
I'm trying to add customView to the MDCTabBarItem using mdc_customView but the items are not taking correct width and the results is as below
if I don't set the mdc_customView value then the result is as expected but without the custom design
Code with mdc_customView
override func parseTabBarItems(data: [SubCategory]) -> [MDCTabBarItem] {
var result: [MDCTabBarItem] = []
var nextX: CGFloat = 15
for cat in data {
guard let count = cat.sub?.count, count > 0 else { continue }
let item = MDCTabBarItem()
item.tag = result.count
let customeView = MDCTabBarCustomView()
customeView.frame = CGRect(x: nextX, y: 0, width: (cat.ref ?? "").sizeOfString(usingFont: .ttrSemiBold10).width, height: 50)
nextX = nextX + 15 + (cat.ref ?? "").sizeOfString(usingFont: .ttrSemiBold10).width
customeView.config(title: cat.ref ?? "")
item.mdc_customView = customeView
result.append(item)
}
return result
}
Code without mdc_customView
override func parseTabBarItems(data: [SubCategory]) -> [MDCTabBarItem] {
var result: [MDCTabBarItem] = []
var nextX: CGFloat = 15
for cat in data {
guard let count = cat.sub?.count, count > 0 else { continue }
let item = MDCTabBarItem(title: cat.ref ?? "", image: nil, tag: result.count)
result.append(item)
}
return result
}
MDCTabBarCustomView
import UIKit
import MaterialComponents.MDCTabBarView
class MDCTabBarCustomView: UIView , MDCTabBarViewCustomViewable {
var titleLabel: UILabel!
var containerView: UIView!
var contentFrame: CGRect
init() {
self.titleLabel = UILabel.newAutoLayout()
self.containerView = TTRView.newAutoLayout()
self.contentFrame = .zero
super.init(frame: .zero)
self.autoresizingMask = []
self.setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func config(title: String) {
self.titleLabel.text = title
}
func setSelected(_ selected: Bool, animated: Bool) {}
private func setup(){
self.addSubview(self.containerView)
self.containerView.addSubview(self.titleLabel)
self.containerView.snp.makeConstraints{
$0.edges.equalToSuperview()
}
self.titleLabel.snp.makeConstraints{
$0.edges.equalToSuperview().offset(5)
}
}
}
The tabBar settings:
self.tabBar.preferredLayoutStyle = .scrollable
after spending all the day trying and learning about this new customView I was able to make it work below is the working code
it was all about the intrinsicContentSize and layoutSubviews
here is the new output
final class MDCTabBarCustomView: UIView , MDCTabBarViewCustomViewable {
var contentFrame: CGRect {
return self.titleLabel.frame
}
var titleLabel: UILabel!
var containerView: UIView!
init(){
self.titleLabel = UILabel.newAutoLayout()
self.containerView = UIView.newAutoLayout()
super.init(frame: .zero)
self.autoresizingMask = []
}
override func layoutSubviews() {
super.layoutSubviews()
if self.containerView.superview != self {
self.addSubview(self.containerView)
}
if self.titleLabel.superview != self.containerView {
self.containerView.addSubview(self.titleLabel)
}
containerView.snp.makeConstraints{
$0.top.leading.equalToSuperview().offset(5)
$0.bottom.trailing.equalToSuperview().offset(-5)
}
titleLabel.snp.makeConstraints{
$0.top.equalToSuperview().offset(5)
$0.bottom.equalToSuperview().offset(-5)
$0.centerX.equalToSuperview()
}
}
override var intrinsicContentSize: CGSize {
return CGSize(width: self.titleLabel.intrinsicContentSize.width + 20, height: self.titleLabel.intrinsicContentSize.height + 20)
}
}
Try preferredLayoutStyle = .scrollableCentered
In my application, I am trying to recreate a live preview picture cropper like in the popular application "Photomath".
Initially, I divided my code into three different classes. The Live preview view (a UIView), an Overlay View (a UIView also), and the actual Cropper View.
Currently, my thought process is to add the CropperView into the Overlay and then the Overlay into the Live Preview View. So far that's working.
I have a UIPanGesture hooked up to the CropperView in order to have it move around eventually. However, here is where the problem resides.
The inside of the cropper is clear or less dark than the surrounding overlay. I've heard that you can achieve this using a mask but have not been successful.
Here is the code for the Live Preview View
class NotationCameraView : UIView {
private var session : AVCaptureSession!
private var stillImageOutput : AVCapturePhotoOutput!
private var videoPreviewLayer : AVCaptureVideoPreviewLayer!
private var previewCropper : OverlayCropper!
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInitializer()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInitializer()
}
private func commonInitializer() {
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
self.setUpLivePreview()
self.setUpOverlayCropper()
print("Set up view")
} else {
AVCaptureDevice.requestAccess(for: .video) { (success) in
if success {
self.setUpLivePreview()
self.setUpOverlayCropper()
} else {
print("Give me your camera REEEEEEEEEEE")
}
}
}
}
/**
Sets up the live preview of the back camera
*/
private func setUpLivePreview() {
self.session = AVCaptureSession()
self.session.sessionPreset = .high
let backCamera = AVCaptureDevice.default(for: AVMediaType.video)
//MARK: Need to have an overlay view so that the user does not see the video preview frame being upscaled
do {
let input = try AVCaptureDeviceInput(device: backCamera!)
self.stillImageOutput = AVCapturePhotoOutput()
if self.session.canAddInput(input) && self.session.canAddOutput(self.stillImageOutput) {
self.session.addInput(input)
self.session.addOutput(self.stillImageOutput)
self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.session)
self.videoPreviewLayer.videoGravity = .resizeAspectFill
self.videoPreviewLayer.connection?.videoOrientation = .portrait
self.layer.addSublayer(videoPreviewLayer)
//Have public methods for starting or stopping the capture session
DispatchQueue.global(qos: .userInitiated).async {
self.session.startRunning()
DispatchQueue.main.async {
self.videoPreviewLayer.frame = self.bounds
}
}
}
} catch {
print("Need to allow permission to the back camera")
}
}
/**
Sets up the overlay cropper for the live preview.
*/
private func setUpOverlayCropper() {
self.previewCropper = OverlayCropper(frame: self.bounds)
self.previewCropper.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.previewCropper)
}
}
Here is the code for the Overlay.
class OverlayCropper : UIView {
private var cropperView : CropperView!
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInitializer()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInitializer()
}
private func commonInitializer() {
self.cropperView = CropperView(frame: CGRect(x: 50, y: 100, width: 100, height: 100))
self.cropperView.backgroundColor = UIColor.blue
self.cropperView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(cropperView)
self.backgroundColor = UIColor.black.withAlphaComponent(0.25)
}
}
and lastly here is the code for the CropperView
class CropperView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInitializer()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInitializer()
}
private func commonInitializer() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture(_:)))
self.addGestureRecognizer(panGesture)
self.makeMask()
}
#objc private func handlePanGesture(_ panGesture : UIPanGestureRecognizer) {
let translation = panGesture.translation(in: self)
//print(translation.x, translation.y)
self.center = CGPoint(x: self.center.x + translation.x, y: self.center.y + translation.y)
panGesture.setTranslation(CGPoint.zero, in: self)
}
private func makeMask() {
let path = CGMutablePath()
path.addRect(CGRect(origin: .zero, size: self.frame.size))
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.path = path
maskLayer.fillRule = .evenOdd
self.layer.mask = maskLayer
self.clipsToBounds = true
}
}
I have an inheritance problem that I'm trying to solve. Typically, I'd just use multi-inheritance here, but Swift doesn't really do that.
Custom UIView
import UIKit
class ValidationView: UIView {
var required:Bool = false
var validRegex:String? = nil
var requiredLbl:UILabel?
private var requiredColor:UIColor = UIColor.red
private var requiredText:String = "*"
private var requiredFont:UIFont = UIFont.systemFont(ofSize: 16.0, weight: UIFont.Weight.bold)
override init(frame: CGRect) {
super.init(frame: frame)
self.setupValidationViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupValidationViews()
}
private func setupValidationViews() {
self.requiredLbl = UILabel(frame: CGRect(x: self.frame.width - 30, y: 30, width: 20, height: 20))
self.styleRequiredLabel()
self.addSubview(self.requiredLbl!)
}
func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?) {
self.requiredColor = color ?? self.requiredColor
self.requiredText = text ?? self.requiredText
self.requiredFont = font ?? self.requiredFont
self.styleRequiredLabel()
}
private func styleRequiredLabel() {
self.requiredLbl?.textColor = self.requiredColor
self.requiredLbl?.text = self.requiredText
self.requiredLbl?.font = self.requiredFont
}
}
Custom UITextField
import Foundation
import UIKit
#IBDesignable open class CustomTextField: UITextField {
#IBInspectable public var borderWidth: CGFloat = 2.0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var borderColor: UIColor = UIColor.lightGray {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable public var cornerRadius: CGFloat = 4.0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
}
}
}
I want that Custom UITextField to also be a ValidationView. I know I could do a protocol and extension and then have my CustomTextField implement that protocol, but that doesn't allow for init overrides. I'd rather not have to change the inits on ever view that implements ValidationView.
Something like this can be accomplished using #arturdev answer. I ended up with this:
import UIKit
class ValidatableProperties {
var required:Bool
var validRegex:String?
var requiredColor:UIColor
var requiredText:String
var requiredFont:UIFont
init(required:Bool, validRegex:String?, requiredColor:UIColor, requiredText:String, requiredFont:UIFont) {
self.required = required
self.validRegex = validRegex
self.requiredText = requiredText
self.requiredColor = requiredColor
self.requiredFont = requiredFont
}
}
protocol Validatable : UIView {
var validatableProperties:ValidatableProperties! { get set }
var requiredLbl:UILabel! { get set }
func setupValidationDefaults()
func setupValidationViews(frame:CGRect)
func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?)
}
extension Validatable {
func setupValidationDefaults() {
let props = ValidatableProperties(required: false, validRegex: nil, requiredColor: UIColor.red, requiredText: "*", requiredFont: UIFont.systemFont(ofSize: 16.0, weight: .bold))
self.validatableProperties = props
}
func setupValidationViews(frame:CGRect) {
self.requiredLbl = UILabel(frame: CGRect(x: frame.width, y: 0, width: 20, height: 20))
self.styleRequiredLabel()
self.addSubview(self.requiredLbl)
}
func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?) {
self.validatableProperties.requiredColor = color ?? self.validatableProperties.requiredColor
self.validatableProperties.requiredText = text ?? self.validatableProperties.requiredText
self.validatableProperties.requiredFont = font ?? self.validatableProperties.requiredFont
self.styleRequiredLabel()
}
private func styleRequiredLabel() {
self.requiredLbl.textColor = self.validatableProperties.requiredColor
self.requiredLbl.text = self.validatableProperties.requiredText
self.requiredLbl.font = self.validatableProperties.requiredFont
}
}
open class ValidationTextField:UITextField, Validatable {
var requiredLbl: UILabel!
var validatableProperties: ValidatableProperties!
override public init(frame: CGRect) {
super.init(frame: frame)
self.setupValidationDefaults()
self.setupValidationViews(frame: frame)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupValidationDefaults()
self.setupValidationViews(frame: self.frame)
}
}
But this requires extending all classes you want validatable into their own custom classes, needing to overwrite the inits every time and call the methods. It works, but it's not ideal and, while not exactly anti-pattern inheritance, certainly has some code-smell to it.
You should make ValidationView as protocol instead of class, and conform your custom classes to that protocol.
ValidatableView.swift
import UIKit
fileprivate var requiredColor = UIColor.red
fileprivate var requiredText = "*"
fileprivate var requiredFont = UIFont.systemFont(ofSize: 16.0, weight: UIFont.Weight.bold)
fileprivate struct AssociatedKeys {
static var lblKey = "_lblKey_"
}
protocol ValidatableView: class {
var required: Bool {get}
var validRegex: String? {get}
var requiredLbl: UILabel? {get}
}
extension ValidatableView where Self: UIView {
var required: Bool {
return false
}
var validRegex: String? {
return nil
}
var requiredLbl: UILabel? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.lblKey) as? UILabel
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.lblKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setupValidation() {
self.requiredLbl = UILabel(frame: CGRect(x: self.frame.width - 30, y: 30, width: 20, height: 20))
self.requiredLbl?.autoresizingMask = .flexibleWidth
self.styleRequiredLabel()
self.addSubview(self.requiredLbl!)
}
func styleRequiredLabel(color:UIColor? = requiredColor, text:String? = requiredText, font:UIFont? = requiredFont) {
self.requiredLbl?.textColor = requiredColor
self.requiredLbl?.text = requiredText
self.requiredLbl?.font = requiredFont
}
}
CustomTextField.swift
#IBDesignable open class CustomTextField: UITextField {
#IBInspectable public var borderWidth: CGFloat = 2.0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var borderColor: UIColor = UIColor.lightGray {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable public var cornerRadius: CGFloat = 4.0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setupValidation()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupValidation()
}
}
extension CustomTextField: ValidatableView { //<- Magic line :)
}
You can create an instance of ValidationView when you instantiate CustomTextField.
Something like this:
#IBDesignable open class CustomTextField: UITextField {
var validationView: ValidationView
override init(frame: CGRect) {
super.init(frame: .zero)
self.validationView = ValidationView()
}
}
I'm building camera App.
I want to preview and photo frame 1:1.
But how can I do that?
I've tried previewView frame change.
self.previewView?.frame.size = CGSize(width: 300, height: 300)
But It does not working.
class CameraViewController: UIViewController {
// MARK: - Properties
// MARK: Declared
var captureSession: AVCaptureSession?
var captureOutput: AVCapturePhotoOutput?
// MARK: IBOutlet
#IBOutlet weak var previewView: PreviewView!
// MARK: - Methods
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.configureInput()
self.configureOutput()
self.configurePreview()
self.runCamera()
}
// MARK: Configure
private func configureInput() {
self.captureSession = AVCaptureSession()
self.captureSession?.beginConfiguration()
self.captureSession?.sessionPreset = .hd4K3840x2160
guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return }
guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice), self.captureSession?.canAddInput(videoDeviceInput) == true else { return }
self.captureSession?.addInput(videoDeviceInput)
}
private func configureOutput() {
let photoOutput = AVCapturePhotoOutput()
self.captureOutput = photoOutput
guard self.captureSession?.canAddOutput(photoOutput) == true else { return }
self.captureSession?.sessionPreset = .photo
self.captureSession?.addOutput(photoOutput)
self.captureSession?.commitConfiguration()
}
private func configurePreview() {
self.previewView?.videoPreviewlayer.session = self.captureSession
}
private func runCamera() {
self.captureSession?.startRunning()
}
}
This is my code.
I made this read after apple's article. (https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/setting_up_a_capture_session)
You could use this to change the preview layer frame to make it fill your preview view:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
previewLayer.frame = cameraView.bounds
previewLayer.videoGravity = .resizeAspectFill
}
I have created Cocoa Touch Class:
Sub Class Of UITextField
And the file Name is MyTextFieldStyle
And here is the MyTextFieldStyle.swift file:
import UIKit
#IBDesignable
class MyTextFieldStyle: UITextField {
#IBInspectable var FavoriteTextColor : UIColor = UIColor.white {
didSet {
self.textColor = FavoriteTextColor
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.textColor = self.FavoriteTextColor
}
}
How can I add these 3 controls,
placeHolder text color
place holder font type
place holder font size
like 'FavoriteTextColor' above ? (Swift 4.2 & Xcode 10)
I'd do my IBDesignables differently. I would put all my final assignment in the same function for an example here is my own custom UITextField I use.
#IBDesignable public class HooopTextfield: UITextField, UITextFieldDelegate {
#IBInspectable public var fontName: String? = "AvenirNext-Bold" {
didSet {
decorate()
}
}
#IBInspectable public var fontSize: CGFloat = 15 {
didSet {
decorate()
}
}
#IBInspectable public var fontColor: UIColor = UIColor.white {
didSet {
decorate()
}
}
#IBInspectable public var customTextAlignment: Int = 0 {
didSet {
decorate()
}
}
#IBInspectable public var borderColor: UIColor = UIColor.white {
didSet {
decorate()
}
}
#IBInspectable public var letterSpacing: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var cornerRadius: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var customPlaceholder: String? = nil {
didSet {
decorate()
}
}
#IBInspectable public var horizontalInset: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var verticalInset: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var selfDelegate: Bool = false {
didSet {
if selfDelegate {
self.delegate = self
}
}
}
#IBInspectable public var borderWidth: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var baseLineOffset: CGFloat = 0 {
didSet {
decorate()
}
}
#IBInspectable public var placeholderColor: UIColor? = nil {
didSet {
decorate()
}
}
#IBInspectable public var requiredColor: UIColor? = nil {
didSet {
decorate()
}
}
#IBInspectable public var requiredCharacter: String = "*"{
didSet {
decorate()
}
}
#IBOutlet public var nextField:HooopTextfield?
/*** more inspectable var can be added **/
override public func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: horizontalInset, dy: verticalInset)
}
override public func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: horizontalInset, dy: verticalInset)
}
func decorate() {
// Setup border and corner radius
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.cgColor
// Setup text style
let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
switch customTextAlignment {
case 2:
paragraphStyle.alignment = .right
break
case 1:
paragraphStyle.alignment = .center
break
default:
paragraphStyle.alignment = .left
break
}
var titleAttributes:[NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: fontColor,
NSAttributedStringKey.kern: letterSpacing,
NSAttributedStringKey.baselineOffset: baseLineOffset,
NSAttributedStringKey.paragraphStyle: paragraphStyle
]
if let _ = fontName {
titleAttributes[NSAttributedStringKey.font] = UIFont(name: fontName!, size: fontSize)
}
if let _ = customPlaceholder {
var placeholderAttributes = titleAttributes
if let _ = placeholderColor {
placeholderAttributes[NSAttributedStringKey.foregroundColor] = placeholderColor
}
let attributedPlaceholder = NSMutableAttributedString(string: customPlaceholder!, attributes: placeholderAttributes)
if let _ = requiredColor {
let range = (customPlaceholder! as NSString).range(of: requiredCharacter)
attributedPlaceholder.addAttribute(NSAttributedStringKey.foregroundColor, value: requiredColor!, range: range)
}
self.attributedPlaceholder = attributedPlaceholder
}
self.defaultTextAttributes = titleAttributes
}
// MARK: - UITexfieldDelegate
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if (nextField != nil) {
nextField?.becomeFirstResponder()
}
return true
}
}
I have basic stuff like fontColor, fontSize, fontName, same things for the placeholder. Then I have more visual things like borderColor, cornerRadius, borderWidth, etc. Finally I also have some inset rect positioning to align everything as I wish right from my storyboard.
I use NSMutableAttributedString for these because they are the most customisable, allowing me to also be able to color required fields and such.
Finally I also have a little #IBOutlet that also allows me to jump from one textfield to the next with the next button on the keyboard when combined with a boolean called selfDelegate.
Ask me any questions you want about this and play around with it you should be able to do anything you want with #IBDesignable I think.
EDIT 1:
Before I forget I recommend using a decorate or equivalent function because the order you apply your changes matter most of the time.
EDIT 2:
Fixed the errors, this was due most likely to swift changing how to use NSAttributedString attributes but strangely enough it seems the placeholderAttributes needed to be [NSAttributedStringKey: Any] but the defaultTextAttributes needed to be [String: Any] don't know why this would be the case. But it should work now.
EDIT 3:
Probably because I have a different version of Xcode or because it was a playground I had a message for defaultTextAttributes but I've refixed it by removing the .rawValue if it was the opposite that needed to be done, know that NSAttributedStringKey.key.rawValue will be a string and you can get the NSAttributedStringKey by using NSAttributedStringKey.init(rawValue:String)