How to make a CGRect animate - swift3

How can I create an animation like this one?
override func draw(_ rect: CGRect)
{
let xPointRect = viewWithTag(1)?.alignmentRect(forFrame: rect).midX
let yPointRect = viewWithTag(1)?.alignmentRect(forFrame: rect).midY
let widthSize = self.viewWithTag(1)?.frame.size.width
let heightSize = self.viewWithTag(1)?.frame.size.height
let sizeRect = CGSize(width: widthSize! * 0.8, height: heightSize! * 0.4)
let rectPlacementX = CGFloat(sizeRect.width/2)
let rectPlacementY = CGFloat(sizeRect.height/2)
let context2 = UIGraphicsGetCurrentContext()
context2?.setLineWidth(2.0)
context2?.setStrokeColor(UIColor.red.cgColor)
context2?.move(to: CGPoint(x: (xPointRect! - rectPlacementX), y: (yPointRect! - rectPlacementY)))
context2?.addLine(to: CGPoint(x: (xPointRect! + rectPlacementX), y: (yPointRect! + rectPlacementY)))
context2?.setLineDash(phase: 3, lengths: dashArray)
context2?.strokePath()
context2.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
UIView.animate(withDuration: 1.0,
delay: 0,
usingSpringWithDamping: 0.15,
initialSpringVelocity: 6.0,
options: .allowUserInteraction,
animations: { context2.transform = CGAffineTransform.identity },
completion: nil)
}
My current code is not working because context2 doesn't contain a member .transfer since it is a CGRect.
How can I make this line animate? Any idea?

A CGRect is not visible, so I do not see why you want to animate it.
I assume what you want to animate the frame of a UIView.
You can change many properties of a UIView in an animation block. Also it's frame:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
someParentView.addSubview(view)
UIView.animate(withDuration: 2.5) {
someUIView.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
}

Related

Gradient not redrawing properly

I am attempting to have a CGRect that has a dynamic gradient. When I use the view models gradient I am able to observe the gradient changing. However the view does not change as expected.
If I use hardcoded gradients in the view which toggle between red and blue I see that the view is redrawing with the updated gradient.
What am I missing?
View Model:
#Published var forceRedraw: Bool = false
#Published var annGradient: Gradient = Gradient(colors: [.red]) {
didSet {
print("ViewModel", annGradient.stops.first!, annGradient.stops.last!)
forceRedraw.toggle()
}
}
View:
struct SchematicView: View {
#EnvironmentObject var vM: AppViewModel
#State var refresh: Bool = false
var body: some View {
VStack(alignment: .center) {
Spacer()
ScrollView {
if refresh {
Canvas { context, size in
let w = size.width
let h = size.height
let grad = vM.annGradient
print("View1", grad.stops.first!, grad.stops.last!)
let annPath = CGRect(x: w/2, y: 0, width: 100, height: h)
context.fill(Path(annPath), with: .linearGradient(grad, startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: h)))
}.frame(width: 300, height: 500)
} else {
Canvas { context, size in
let w = size.width
let h = size.height
let grad = vM.annGradient
print("View2", grad.stops.first!, grad.stops.last!)
let annPath = CGRect(x: w/2, y: 0, width: 100, height: h)
context.fill(Path(annPath), with: .linearGradient(grad, startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: h)))
}.frame(width: 300, height: 500)
}
}
.syncBool($vM.forceRedraw, with: $refresh)
.onAppear(perform: {
refresh = vM.forceRedraw
})

How to position custom shape in a center of a button

I've found an animated custom Tab bar tutorial and tried to reproduce it. But now I'm stuck on the moment where I need to place the center of my custom shape in the center of the button every time it is pressed. Would be very appreciate if somebody could explain to me why does midX is located between buttons?
Here is expecting result :
And here is what I get when taping on the button with code below:
Code:
struct TabBar: View{
#State private var selectedTab: Tabs.RawValue = Tabs.home.rawValue
#State private var xAxisForCurve: CGFloat = 80
let screenWidth: CGFloat
private var tabs : [String]{
var _tabs: [String] = []
Tabs.allCases.forEach { tab in
_tabs.append(tab.rawValue)
}
return _tabs
}
var body: some View{
HStack{
ForEach(tabs, id: \.self) { tab in
GeometryReader { button in
Button(action: {
withAnimation(.spring()){
self.selectedTab = tab
self.xAxisForCurve = button.frame(in: .global).midX
}
}) {
Image(systemName: tab)
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.foregroundColor(self.selectedTab == tab ? Color.blue : Color.gray)
}
}
.frame(width: 28, height: 28)
if tab != tabs.last{
Spacer().frame(width: 65)
}
}
}
.frame(width: screenWidth * multiplayerForTabFrameOnDifferentDevices())
.padding(.vertical)
.background(Color.white.clipShape(MyShape(xAxis: self.xAxisForCurve)))
.cornerRadius(20)
}
}
struct MyShape: Shape{
var xAxis: CGFloat
func path(in rect: CGRect) -> Path {
Path{ path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
let upperLeftCorner = CGPoint(x: self.xAxis - 50 , y: rect.minY)
let upperRightCorner = CGPoint(x: self.xAxis + 50, y: rect.minY)
let curveCenter = CGPoint(x: self.xAxis, y: rect.height * 0.55)
let control1 = CGPoint(x: self.xAxis - 25 , y: 0)
let control2 = CGPoint(x: self.xAxis - 25 , y: 35)
let control4 = CGPoint(x: self.xAxis + 25 , y: 0)
let control3 = CGPoint(x: self.xAxis + 25 , y: 35)
path.move(to: upperLeftCorner)
path.addCurve(to: curveCenter, control1: control1, control2: control2)
path.addCurve(to: upperRightCorner, control1: control3, control2: control4)
}
}
}
You're taking global frame with frame(in: .global): it's frame related to the device screen.
But your shape needs position related to your TabBar bounds.
You can solve it by adding a named coordinate space:
.frame(width: screenWidth * multiplayerForTabFrameOnDifferentDevices())
.coordinateSpace(name: "container")
And getting button frame in this coordinate space:
xAxisForCurve = button.frame(in: .named("container")).midX
p.s. from your code it looks like you're trying to animate your shape. You need to add the following lines to your shape to make it animatable. More info can be found here
var animatableData: CGFloat {
get { xAxis }
set { xAxis = newValue }
}

How to scale Path in SwiftUI?

I have some problems to scale a Path accordingly to my purpose. I'm getting from an API some SVG data that I would like to draw in my view. In order to make it simple let's say this is what I get from the API:
extension UIBezierPath {
static var rectangle: UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 200, y: 100))
path.addLine(to: CGPoint(x: 100, y: 300))
path.addLine(to: CGPoint(x: 300, y: 300))
path.addLine(to: CGPoint(x: 200, y: 100))
return path
}
}
When I display this path in my view everything works fine.
Since the SVG coordinates from the API have an offset and are sometimes bigger than the display screen of the iPhone, I need to scale them down/up to a specific size and center them in my view. In order to do that I tried this transformation:
struct ScaledShapeView: Shape {
let bezier: UIBezierPath
func path(in rect: CGRect) -> Path {
let bounds = bezier.bounds
let scaleX = rect.size.width/bounds.size.width
let scaleY = rect.size.height/bounds.size.height
let scale = max(scaleX, scaleY)
return Path(bezier.cgPath).applying(CGAffineTransform(scaleX: scale, y: scale))
}
}
and uses it in my view:
struct TestView: View {
var body: some View {
ScaledShapeView(bezier: .rectangle)
.frame(width: 100, height: 100)
}
}
But this is my result:
I'm not sure what exactly the problem is. I would like to achieve that the shape is in the middle of the frame.
#SwiftPunk:
When I try
var body: some View {
Path(UIBezierPath.logo1.cgPath)
.frame(width: 100, height: 100)
.scaleEffect(CGSize(width: 0.5, height: 0.5))
}
I get this result:
Normalized each X and Y position for drawing the points between 0 and 1, where 0 means the top edge or the leading edge, and 1 means the bottom edge or the trailing edge.
Also,we’re going to find the minimum of width and height so we can scale our Bezier path proportionally, so it stays the same shape as it grows.
import SwiftUI
extension UIBezierPath{
static var rectangle: UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.5, y: 0.2))
path.addLine(to: CGPoint(x: 0.2, y: 0.8))
path.addLine(to: CGPoint(x: 0.8, y: 0.8))
path.addLine(to: CGPoint(x: 0.5, y: 0.2))
return path
}
}
struct ScaledShapeView:Shape{
var bezier:UIBezierPath
func path(in rect: CGRect) -> Path {
let path = Path(bezier.cgPath)
let multiplier = min(rect.width,rect.height)
let transform = CGAffineTransform(scaleX:multiplier , y: multiplier)
return path.applying(transform)
}
}
struct TestView: View {
#State private var endAmount: CGFloat = 0
var body: some View {
ScaledShapeView(bezier: UIBezierPath.rectangle)
.frame(width: 300, height: 300)
}
}
Output-:

how to add textfield as a custom view in leftBarButtonItems, here textfield need to be capable to typing

how to add a new view that view having textfield and buttons in it and how this view can be added in leftbarbuttonitmes, and textfield need to be capable to typing.
Right now I have added this view to leftbarbuttonitems successfully but my textfield is not capable to type. Is there any way by which we can type if we add in leftbarbuttonitems.
Here it is: Just paste below code in your viewDidLoad method.
let viewLeftButton: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 160.0, height: 32.0))
let textField: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 100.0, height: viewLeftButton.frame.size.height))
textField.borderStyle = .roundedRect
textField.font = UIFont.systemFont(ofSize: 14.0)
textField.placeholder = "Type here..."
let button: UIButton = UIButton(frame: CGRect(x: textField.frame.size.width + 8, y: 0, width: 44.0, height: viewLeftButton.frame.size.height))
button.setTitle("Click", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 14.0)
button.setTitleColor(.blue, for: .normal)
viewLeftButton.addSubview(textField)
viewLeftButton.addSubview(button)
self.navigationItem.hidesBackButton = true
let leftBarButton = UIBarButtonItem(customView: viewLeftButton)
self.navigationItem.leftBarButtonItem = leftBarButton

TableView Expand/Collapse with Swift 3

I did collapse/expand table cell in swift 3. But, I would like to add some padding bottom to the title header. In my screenshot, there is no padding between Title 1 and Title 2.
Another problem is that how can I move arrow image to the right?
Here is my code.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 28))
var imageView = UIImageView()
if (expandSections.contains(section)) {
imageView = UIImageView.init(frame: CGRect(x: 7, y: 5, width: 25, height: 25))
imageView.image = UIImage(named: "down_image")
} else {
imageView = UIImageView.init(frame: CGRect(x: 7, y: 5, width: 25, height: 25))
imageView.image = UIImage(named: "up_image")
}
let headerTitle = UILabel.init(frame: CGRect(x: 38, y: 4, width: 250, height: 28))
headerTitle.text = sectionData[section]
headerTitle.textColor = UIColor.white
let tappedSection = UIButton.init(frame: CGRect(x: 0, y: 0, width: headerView.frame.size.width, height: headerView.frame.size.height))
tappedSection.addTarget(self, action: #selector(sectionTapped), for: .touchUpInside)
tappedSection.tag = section
headerView.addSubview(imageView)
headerView.addSubview(headerTitle)
headerView.addSubview(tappedSection)
headerView.backgroundColor = UIColor.lightGray
return headerView
}
Try this, In here I added another view inside of headerView. Then only u can add padding in headerView.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 28))
let internalView = UIView.init(frame: CGRect(x: 0, y: 0, width: 300, height: 25))
var imageView = UIImageView()
if (expandSections.contains(section)) {
imageView = UIImageView.init(frame: CGRect(x: internalView.frame.size.width - 10 , y: 5, width: 25, height: 25))
imageView.image = UIImage(named: "down_image")
} else {
imageView = UIImageView.init(frame: CGRect(x: internalView.frame.size.width - 10 , y: 5, width: 25, height: 25))
imageView.image = UIImage(named: "up_image")
}
let headerTitle = UILabel.init(frame: CGRect(x: 38, y: 4, width: 250, height: 28))
headerTitle.text = sectionData[section]
headerTitle.textColor = UIColor.white
let tappedSection = UIButton.init(frame: CGRect(x: 0, y: 0, width: headerView.frame.size.width, height: headerView.frame.size.height))
tappedSection.addTarget(self, action: #selector(sectionTapped), for: .touchUpInside)
tappedSection.tag = section
internalView.addSubview(imageView)
internalView.addSubview(headerTitle)
internalView.addSubview(tappedSection)
headerView.addSubview(internalView)
headerView.backgroundColor = UIColor.lightGray
return headerView
}