UIButton I need to create a button with curved edges and borders but I'm not sure how to do it - uibutton

I have been searching all over google trying to find an answer to this but can't. I need to create a buttin that has rounded or curved edges as well as adding a border to it. Nothing I yhave tried has worked. please help

I would suggest showing us what you have already tried; that said, here is what I use:
#IBDesignable class MyButton: UIButton
{
#IBInspectable var borderColor:UIColor? {
set {
layer.borderColor = newValue!.cgColor
}
get {
if let color = layer.borderColor {
return UIColor(cgColor:color)
}
else {
return nil
}
}
}
#IBInspectable var borderWidth:CGFloat {
set {
layer.borderWidth = newValue
}
get {
return layer.borderWidth
}
}
override func layoutSubviews() {
super.layoutSubviews()
updateCornerRadius()
}
#IBInspectable var rounded: Bool = false {
didSet {
updateCornerRadius()
}
}
func updateCornerRadius() {
layer.cornerRadius = rounded ? frame.size.height / 2 : 0
}
}

Related

How do I update a SwiftUI View in UIKit when value changes?

I am trying to integrate a SwiftUI view that animates on changes to a #State variable (originally progress was #State private progress: CGFloat = 0.5 in the SwiftUI View), into an existing UIKit application. I have read through lots of search results on integrating SwiftUI into UIKit (#State, #Binding, #Environment, etc.), but can't seem to figure this out.
I create a very simple example of what I am attempting to do, as I believe once I see this answer I can adopt it to my existing application.
The Storyboard is simply View controller with a UISlider. The code below displays the SwiftUI view, but I want it to update as I move the UISlider.
import SwiftUI
class ViewController: UIViewController {
var progress: CGFloat = 0.5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim(progress: progress))
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
childView.didMove(toParent: self)
}
#IBAction func sliderAction(_ sender: UISlider) {
progress = CGFloat(sender.value)
print("Progress: \(progress)")
}
}
struct Animate_Trim: View {
var progress: CGFloat
var body: some View {
VStack(spacing: 20) {
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
Spacer()
}.font(.title)
}
}```
If you do not want to use NotificationCenter. You could use just #Published and assign or sink.
I wrote a working example in a Playground to show the concept:
//This code runs on Xcode playground
import Combine
import SwiftUI
class ObservableSlider: ObservableObject {
#Published public var value: Double = 0.0
}
class YourViewController {
var observableSlider:ObservableSlider = ObservableSlider()
private var cancellables: Set<AnyCancellable> = []
let hostController = YourHostingController() // I put it here for the sake of the example, but you do need a reference to the Hosting Controller.
init(){ // In a real VC this code would probably be on viewDidLoad
let swiftUIView = hostController.rootView
//This is where values of SwiftUI view and UIKit get glued together
self.observableSlider.$value.assign(to: \.observableSlider.value, on: swiftUIView).store(in:&self.cancellables)
}
func updateSlider() {
observableSlider.value = 8.5
}
}
// In real app it would be something like:
//class YourHostingController<YourSwiftUIView> UIHostingController
class YourHostingController {
var rootView = YourSwiftUIView()
//In a real Hosting controller you would do something like:
// required init?(coder aDecoder: NSCoder){
// super.init(coder: aDecoder, rootView: YourSwiftUIView())
// }
}
struct YourSwiftUIView: View{
var body: some View {
EmptyView() // Your real SwiftUI body...
}
#ObservedObject var observableSlider: ObservableSlider = ObservableSlider()
func showValue(){
print(observableSlider.value)
}
init(){
print(observableSlider.value)
}
}
let yourVC = YourViewController() // Inits view and prints 0.0
yourVC.updateSlider() // Updates from UIKit to 8.5
yourVC.hostController.rootView.showValue() // Value in SwiftUI View is updated (prints 8.5)
The accepted answer actually doesn't answer the original question "update a SwiftUI View in UIKit..."?
IMHO, when you want to interact with UIKit you can use a notification to update the progress view:
extension Notification.Name {
static var progress: Notification.Name { return .init("progress") }
}
class ViewController: UIViewController {
var progress: CGFloat = 0.5 {
didSet {
let userinfo: [String: CGFloat] = ["progress": self.progress]
NotificationCenter.default.post(Notification(name: .progress,
object: nil,
userInfo: userinfo))
}
}
var slider: UISlider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
slider.addTarget(self, action: #selector(sliderAction(_:)), for: .valueChanged)
slider.frame = CGRect(x: 0, y: 500, width: 200, height: 50)
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim())
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
view.addSubview(slider)
childView.didMove(toParent: self)
}
#IBAction func sliderAction(_ sender: UISlider) {
progress = CGFloat(sender.value)
print("Progress: \(progress)")
}
}
struct Animate_Trim: View {
#State var progress: CGFloat = 0
var notificationChanged = NotificationCenter.default.publisher(for: .progress)
var body: some View {
VStack(spacing: 20) {
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
.onReceive(notificationChanged) { note in
self.progress = note.userInfo!["progress"]! as! CGFloat
}
Spacer()
}.font(.title)
}
}
Combine is your friend ...
create the model
update the model from UISlider, or any other part of your application
use the model to update your SwiftUI.View
I did simple example, solely with SwiftUI, but using the same scenario
import SwiftUI
class Model: ObservableObject {
#Published var progress: Double = 0.2
}
struct SliderView: View {
#EnvironmentObject var slidermodel: Model
var body: some View {
// this is not part of state of the View !!!
// the bindig is created directly to your global EnnvironmentObject
// be sure that it is available by
// creating the SwiftUI view that provides the window contents
// in your SceneDelegate.scene(....)
// let model = Model()
// let contentView = ContentView().environmentObject(model)
let binding = Binding<Double>(get: { () -> Double in
self.slidermodel.progress
}) { (value) in
self.slidermodel.progress = value
}
return Slider(value: binding, in: 0.0 ... 1.0)
}
}
struct ContentView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
Circle()
.trim(from: 0, to: CGFloat(model.progress)) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
SliderView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(Model())
}
}
and all is nicely working together

Mkmap iOS11 clusters doesn't split up after max zoom, how to set it up?

First, my code is perfectly running.
I have well set up and mapView.register my annotation markers and cluster.
When I zoom out the annotations fusion as expected in my cluster views,
when I zoom in, same good result, except at a certain point. When too many annotations are too close from each other, the cluster view doesn't split up into my two annotation views anymore.
So I search a way to be able to setup this "zoom level" that will makes appear my two annotations even if there are really close from each other.
Here are my cluster views with a high zoom on the map:
Here if I zoom at the maximum:
Well, one of the cluster views split into two, but doesn't reveal the 4 annotations.
I also try to setup the displayPriority to be higher for my two annotations, than the cluster view, but the result is still the same.
Any ideas ?
You will need to keep track of the zoom level of the map, and reload your annotations when you cross a zoom level that you specify.
private let maxZoomLevel = 9
private var previousZoomLevel: Int?
private var currentZoomLevel: Int? {
willSet {
self.previousZoomLevel = self.currentZoomLevel
}
didSet {
// if we have crossed the max zoom level, request a refresh
// so that all annotations are redrawn with clustering enabled/disabled
guard let currentZoomLevel = self.currentZoomLevel else { return }
guard let previousZoomLevel = self.previousZoomLevel else { return }
var refreshRequired = false
if currentZoomLevel > self.maxZoomLevel && previousZoomLevel <= self.maxZoomLevel {
refreshRequired = true
}
if currentZoomLevel <= self.maxZoomLevel && previousZoomLevel > self.maxZoomLevel {
refreshRequired = true
}
if refreshRequired {
// remove the annotations and re-add them, eg
let annotations = self.mapView.annotations
self.mapView.removeAnnotations(annotations)
self.mapView.addAnnotations(annotations)
}
}
}
private var shouldCluster: Bool {
if let zoomLevel = self.currentZoomLevel, zoomLevel <= maxZoomLevel {
return false
}
return true
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// https://stackoverflow.com/a/40616239/883413
let zoomWidth = mapView.visibleMapRect.size.width
let zoomLevel = Int(log2(zoomWidth))
self.currentZoomLevel = zoomLevel
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// for me, annotation reuse doesn't work with clustering
let annotationView = CustomAnnotationView(annotation: annotation)
if self.shouldCluster {
annotationView.clusteringIdentifier = "custom-id"
} else {
annotationView.clusteringIdentifier = nil
}
return annotationView
}
In my case, ! EVERY TIME ! I didn't update the clusteringIdentifier
in "func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation)"
When the MKAnnotationView is reused by the mapView.dequeueReusableAnnotationView(withIdentifier: "identifier", for: annotation), the clusteringIdentifier will be nil. (reset)
That's the reason why the clusters doesn't work.
AnnotationView.swift
import MapKit
// MARK: - Define
struct AnnotationViewInfo {
static let identifier = "AnnotationView"
}
final class AnnotationView: MKAnnotationView {
// MARK: - Initializer
override init(annotation: MKAnnotation!, reuseIdentifier: String!) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
setView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setView()
}
// MARK: - Value
// MARK: Public
override var annotation: MKAnnotation? {
willSet { update(annotation: newValue) }
}
// MARK: - Function
// MARK: Private
private func setView() {
if #available(iOS 11.0, *) {
collisionMode = .rectangle
clusteringIdentifier = AnnotationViewInfo.identifier
}
canShowCallout = true
image = #imageLiteral(resourceName: "pin01").resizedImage(size: CGSize(width: #imageLiteral(resourceName: "pin01").size.width/4.0, height: #imageLiteral(resourceName: "pin01").size.height/4.0), scale: 1.0)
}
private func update(annotation: MKAnnotation?) {
if #available(iOS 11.0, *) {
clusteringIdentifier = AnnotationViewInfo.identifier
}
// TODO: Update the annotationView
}
}
MKMapViewDelegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if #available(iOS 11.0, *) {
switch annotation {
case is PointAnnotation: return mapView.dequeueReusableAnnotationView(withIdentifier: AnnotationView1Info.identifier, for: annotation)
case is MKClusterAnnotation: return mapView.dequeueReusableAnnotationView(withIdentifier: ClusterAnnotationViewInfo.identifier, for: annotation)
case is MKUserLocation: return nil
default: return nil
}
} else {
return nil
}
}
Key Point (You must update the "clusteringIdentifier" every time.)
private func update(annotation: MKAnnotation?) {
if #available(iOS 11.0, *) {
clusteringIdentifier = AnnotationViewInfo.identifier
}
// TODO: Update the annotationView
}
}
Sample Project Here

Presenting UIViewController touch events not registering

I have a custom transition event going on with my view controllers where view controller a (VC_A) will present (VC_B).
Now when doing the transition, VC_A will still exist, but VC_B will not receive any touch events. Now if I remove the view from VC_A, VC_B will get touch events. I am not sure what I am missing in my transition that tells the app to be sending all touch events to VC_B not VC_A. I have tried setting the first responder, but that did not work, if somebody could tell me what I am missing, it would be appreciated.
//
// AnimationController.swift
// MarineWars
//
// Created by Anthony Randazzo on 5/6/15.
// Copyright (c) 2015 Anthony Randazzo. All rights reserved.
//
import Foundation
import UIKit
class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .Presenting)
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .Dismissing)
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
{
return nil;
}
}
private var inSuperView : UIView?;
private var toSuperView : UIView?;
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case Presenting
case Dismissing
}
var animationTransitionOptions: UIViewAnimationOptions
var inView : UIView?;
var toView : UIView?;
var fromView : UIView?;
init(transitionType: TransitionType) {
switch transitionType {
case .Presenting:
animationTransitionOptions = .TransitionFlipFromLeft
case .Dismissing:
animationTransitionOptions = .TransitionFlipFromRight
}
super.init()
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
inView = transitionContext.containerView()
toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)?.view // if iOS 8 only, I'd use `viewForKey`, instead
fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view
if(animationTransitionOptions == .TransitionFlipFromLeft)
{
NSTimer.scheduledTimerWithTimeInterval(transitionDuration(transitionContext)/2, target: self, selector: Selector("fireHalfwayIn"), userInfo: nil, repeats: false);
}
else if(animationTransitionOptions == .TransitionFlipFromRight)
{
NSTimer.scheduledTimerWithTimeInterval(transitionDuration(transitionContext)/2, target: self, selector: Selector("fireHalfwayOut"), userInfo: nil, repeats: false);
}
var scene = GameScene.instance;
scene!.paused = false;
scene!.view?.paused = false;
UIView.transitionFromView(fromView!, toView: toView!, duration: transitionDuration(transitionContext), options: animationTransitionOptions | .CurveEaseInOut | .AllowAnimatedContent) { finished in
transitionContext.completeTransition(true)
NSNotificationCenter.defaultCenter().postNotificationName("DropNotifications",object:nil);
//inSuperView is our parent
if(inSuperView == nil)
{
inSuperView = self.fromView;
println(self.fromView);
for view in self.fromView!.subviews
{
for subview in view.subviews
{
if(subview.isMemberOfClass(GameSceneView))
{
self.fromView!.resignFirstResponder()
self.toView!.becomeFirstResponder();
(subview as! GameSceneView).removeScene();
return;
}
}
if(view.isMemberOfClass(GameSceneView))
{
self.fromView!.resignFirstResponder()
self.toView!.becomeFirstResponder();
(view as! GameSceneView).removeScene();
return;
}
}
}
else
{
for view in self.toView!.subviews
{
for subview in view.subviews
{
if(subview.isMemberOfClass(GameSceneView))
{
(subview as! GameSceneView).createScene();
break;
}
}
}
}
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 1.0
}
func fireHalfwayIn()
{
//println("\nFire In\n");
var scene = GameScene.instance;
//scene!.paused = false;
if(scene!.player.grid.parent != nil)
{
scene!.currentPlayer = scene!.enemy;
scene!.player.grid.removeFromParent();
scene!.addChild(scene!.enemy.grid);
var player = scene!.currentPlayer;
player!.grid.position = GameScene.fixPositionOnScreen(CGPointMake(toView!.frame.width/2 - player!.gridSize.width/2, toView!.frame.height/2 - player!.gridSize.height/2),currentPlayer: player!);
}
}
func fireHalfwayOut()
{
// println("\nFire Out\n");
var scene = GameScene.instance;
if(scene!.enemy.grid.parent != nil)
{
scene!.enemy.grid.removeFromParent();
scene!.currentPlayer = scene!.player;
scene!.addChild(scene!.player.grid);
}
}
}
OK I found the problem, I need to add the view to the content subview
if(self.presenting)
{
self.inView!.addSubview(self.toView!);
}
This of course leads to a bug when a context is completed, removing the from view from the key window.
So I needed to add this line after the context completed:
UIApplication.sharedApplication().keyWindow!.addSubview(self.toView!);
to get it to properly work

In swift is is possible to separate set, get, willSet, and willGet from the declaration of the property?

I will start by showing you some properties from a project I'm working on...
/** Properties **/
var coordinates: Coordinates
var text: NSString = "" {
didSet {
self.needsDisplay = true
}
}
var boxViewGroup: GridBoxViewGroup
var isLocked: Bool = false {
willSet {
if isSelected || !newValue {
boxViewGroup.selectedBox = nil
}
}
}
var isSelected: Bool {
get {
if boxViewGroup.selectedBox {
return boxViewGroup.selectedBox === self
}
else {return false}
}
}
var invertedFrame: NSRect {
get {
return NSRect(x: frame.origin.x,
y: superview.bounds.height - frame.origin.y,
width: bounds.size.width,
height: bounds.size.height)
}
set {
self.frame = NSRect(x: newValue.origin.x,
y: superview.bounds.height - newValue.origin.y - newValue.height,
width: newValue.width,
height: newValue.height)
}
}
That looks a little messy right. So my question is is it possible to put get, set, willGet, and willSet methods in a separate place so that my property declarations can look like this...
var coordinates: Coordinates
var boxViewGroup: GridBoxViewGroup
var isSelected: Bool
var invertedFrame: NSRect
See like this I can actually tell what properties there are.
It's possible by spiting in into 2 classes. You can override property declaration in subclass and add Property Observers
class DataA {
var coordinates: Coordinates
var boxViewGroup: GridBoxViewGroup
var isSelected: Bool
var invertedFrame: NSRect
}
class A : DataA {
override var coordinates: Coordinates {
didSet {
//add code
}
willSet(newValue) {
//add code
}
}
}
Read more about Property Overriding in apple documentation

How to change NSScroller color?

I'm trying to change the color of the scroller itself, it seems I need to overwrite something around drawKnob but I couldn't find any method which seemed appropriate.
One way I thought about was to inject a new style and get scroller to use it but again, I couldn't find anything about NSColor in NSScroller header.
Any idea?
No need to redraw from scratch. It's as simple as this:
import AppKit
class CyanideScroller: NSScroller {
private var _bkgrColour: NSColor = NSColor.black
private var _knobColour: NSColor = NSColor.white
private var isHorizontal = false
var bkgrColour: NSColor {
get { return _bkgrColour}
set { _bkgrColour = newValue}
}
var knobColour: NSColor {
get { return _knobColour}
set { _knobColour = newValue}
}
override var frame: CGRect {
didSet {
let size = frame.size
isHorizontal = size.width > size.height
}
}
override func draw(_ dirtyRect: NSRect) {
_bkgrColour.setFill()
dirtyRect.fill()
self.drawKnob()
}
override func drawKnob() {
_knobColour.setFill()
let dx, dy: CGFloat
if isHorizontal {
dx = 0; dy = 3
} else {
dx = 3; dy = 0
}
let frame = rect(for: .knob).insetBy(dx: dx, dy: dy)
NSBezierPath.init(roundedRect: frame, xRadius: 3, yRadius: 3).fill()
}
}
I think you will have to write a subclass, and override drawKnob, maybe drawKnobSlotInRect: too depending on what you want to change.
The CyanideScroller class is nice, I've just updated it a little bit. Above all I have made sure that the knob is drawn only if necessary. This is my class:
import AppKit
/**
A custom object that controls scrolling of a document view
within a scroll view or other type of container view.
*/
class CustomScroller: NSScroller {
/**
Depository of the horizontal Boolean value.
*/
private var _isHorizontal: Bool = false
/**
A Boolean value that indicates whether the scroller is horizontal.
*/
public var isHorizontal: Bool {
return _isHorizontal
}
/**
The background color.
*/
public var backgroundColor: NSColor = NSColor.black
/**
The knob color.
*/
public var knobColor: NSColor = NSColor.white
/**
A Boolean value that indicates whether the scroller draws its background.
*/
public var drawsBackground: Bool = true
override var frame: CGRect {
didSet {
let size = frame.size
_isHorizontal = size.width > size.height
}
}
override func draw(_ dirtyRect: NSRect) {
if drawsBackground {
backgroundColor.setFill()
dirtyRect.fill()
}
if (usableParts == .allScrollerParts) {
drawKnob()
}
}
/**
Draws the knob.
*/
override func drawKnob() {
knobColor.setFill()
let dx, dy: CGFloat
if _isHorizontal {
dx = 0
dy = 3
} else {
dx = 3
dy = 0
}
let frame = rect(for: .knob).insetBy(dx: dx, dy: dy)
let roundedPath = NSBezierPath(roundedRect: frame, xRadius: 3, yRadius: 3)
roundedPath.fill()
}
}