Wednesday, October 4, 2017

Thước kẻ

Thước kẻ là ứng dụng không dễ cũng không khó, đòi hỏi kỹ năng tính toán, xử lý một chút.
Hãy tạo một class mới là subclass của UIView.


Đây là chỗ ta vẽ các đường kẻ lên màn hình iphone để giả lập các vạch của cái thước kẻ. Khi người dùng chạm tay vào màn hình, sẽ có cái vạch di chuyển theo tay chạm và dòng chữ ghi tọa độ vị trí cả bằng cm và inch.


Khai báo các biến và thêm các hàm để bắt chạm tay vào màn hình.
Copy lên trên dòng overridefunc drawRect
var x=0.0//so inch
var den=0
var diem=0
var dem=0
let r = UIScreen.mainScreen().bounds.size.width
let c = UIScreen.mainScreen().bounds.size.height
var l = CGPointZero
var f = CGPointZero
overridefunc touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
iflet touch = touches.first {
f   = touch.locationInView(self)
l = f
setNeedsDisplay()
        }
    }
overridefunc touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
iflet touch = touches.first {
l = touch.locationInView(self)
setNeedsDisplay()
        }
    }
overridefunc touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
iflet touch = touches.first {
l = touch.locationInView(self)
setNeedsDisplay()
        }
    }

Ta khai báo các biến đếm để phân biệt người dùng chọn cm hay inch, lấy chiều dài rộng màn hình. Biến l để lấy toạ độ tay chạm người dùng.
Ba hàm override touch là thủ tục để lấy toạ độ tay người dùng chạm màn hình.
Bây giờ có một vấn đề là làm sao để vẽ 1cm thì dài đúng 1cm.
Ta sẽ cần biết kích thước thực tế đường chéo màn hình tức số inch, sau đó tính để xem 1cm tương đương bao nhiêu pixel, từ đó vẽ đúng tỷ lệ tính được.
Biến x là số inch, ta sẽ lấy nó sau này bên class chính, bây giờ cứ dùng để tính tỷ lệ.
Copy các dòng sau vào trong hàm drawRect
let tcanh=r*r+c*c
var so=x*25.4
        so=so*so;
let socann=Double(tcanh)/so;
let socan = sqrt(socann)
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 0.5)
var socanb=socan
ifden%2==1{
            socanb=socan/2.54
        }


Đây ta tính đường chéo theo pixel rồi chia với kích thước thật được socan là tỷ lệ cần có.
Lệnh if tức là khi người dùng chọn là inch thì phải chia cho 2.54 mới ra tỷ lệ cần dùng.
Copy hàm sau lên trên ngoặc đóng cuối cùng.
func round2(a:Double)->Double{
let mu = pow(10.0,2.0)
let r=round(a*mu)/mu
return r
    }
Hàm round2() để làm tròn số mm, inch tọa độ của cái gạch đỏ ta sẽ vẽ.
Bây giờ hãy vẽ các gạch và chữ để cho nó giống một cái thước kẻ.
Copy vào trong hàm drawRect tiếp xuống dưới.
for i in0..<250 {
if(i%5==0){
let kl=i/5;
if(kl%2==0){
CGContextMoveToPoint(context, r-CGFloat(socanb*Double(7)),CGFloat(socan*Double(i)))
/* And end it at this point */
CGContextAddLineToPoint(context, r,CGFloat(socan*Double(i)))
                }
else{
CGContextMoveToPoint(context, r-CGFloat(socanb*Double(5)),CGFloat(socan*Double(i)))
/* And end it at this point */
CGContextAddLineToPoint(context, r,CGFloat(socan*Double(i)))
                }
            }
else{

CGContextMoveToPoint(context, r-CGFloat(socanb*Double(3)),CGFloat(socan*Double(i)))
/* And end it at this point */
CGContextAddLineToPoint(context, r,CGFloat(socan*Double(i)))
            }
        }
for i in1..<25 {
let string =   String(i)
            string.drawAtPoint(CGPointMake(r-70, CGFloat(10*socan*Double(i))-10),
                               withAttributes: [NSFontAttributeName : UIFont(name: "Arial", size: 24.0)!])
        }
UIColor.blackColor().set()
CGContextStrokePath(context)
Để cho tiện ta sẽ làm xong luôn bên class vẽ này, sau sẽ sang class chính mới chạy nó.
Trong lệnh for i in 0..<250 ta vẽ ra 3 loại đường kẻ dài ngắn khác nhau tuỳ thứ tự của chúng. Ta chia cho 5 để biết đường nào là được nửa phân, đủ 1 phân thì vẽ dài ra, còn lại vẽ ngắn là các kẻ ly thường.
Khoảng cách tuỳ socan của cm hay socanb của inch mà khác nhau.
Lệnh for i in1..<25 {  vẽ ra các số từ 1-25, thực ra ta có thể để 30,40 nhưng không có thiết bị nào dài đến thế kể cả ipad pro nên cứ để 25 là được rồi.
Giờ ta muốn có cái gạch ngang màu đỏ khi vừa vào màn hình. Copy đoạn sau tiếp xuống dưới lệnh for.
ifl.y == 0 {
let conn = UIGraphicsGetCurrentContext()
UIColor.redColor().set()
CGContextSetLineWidth(conn, 0.5)
CGContextMoveToPoint(conn, 0, c/2)
//vẽ line khi chưa có chạm
CGContextAddLineToPoint(conn, r, c/2)
UIColor.redColor().set()
CGContextStrokePath(conn)
        }
l.y == 0 tức toạ độ điểm chạm chưa có gì, là lúc ban đầu mở vào.
Bây giờ, khi người dùng chạm tay vào màn hình, ta phải vẽ kẻ gạch di chuyển theo chạm và số cm của kẻ gạch đó trên màn hình.
Copy tiếp xuống dưới, vẫn trong hàm drawRect.
ifl.y>0{
let con = UIGraphicsGetCurrentContext()
UIColor.redColor().set()
/* Set the color that we want to use to draw the line */
CGContextMoveToPoint(con, 0, l.y)
/* And end it at this point */
CGContextAddLineToPoint(con, r, l.y)
CGContextStrokePath(con)
let pa=CGPathCreateMutable()
CGContextAddPath(context, pa)
CGContextDrawPath(context, CGPathDrawingMode(rawValue: 1)!)
ifden%2==0{
//let cmm = Double(r-l.x)/socan
let cm = Double(l.y)/socan
let cc=round2(cm)
let tring =   String(cc) + " mm"
                tring.drawAtPoint(CGPointMake(r/6, l.y ), withAttributes: [NSForegroundColorAttributeName: UIColor.redColor(), NSFontAttributeName: UIFont(name: "HelveticaNeue", size: 27.0)!])
            }
else{
//đây là inch
// let cmm = Double(r-l.x)/socan
let cm = Double(l.y)/socan
let cc=round2(cm/10)
let tring =   String(cc) + " inch"
                tring.drawAtPoint(CGPointMake(r/6, l.y ), withAttributes: [NSForegroundColorAttributeName: UIColor.redColor(), NSFontAttributeName: UIFont(name: "HelveticaNeue", size: 27.0)!])
            }
        }
l.y>0 tức là có chạm tay rồi, ta vẽ cái gạch theo toạ độ l.y, vì tay di chuyển nên y cũng di chuyển dọc màn hình, tức là cái gạch nó di chuyển theo vị trí tay chạm.
Trong lệnh if khi biến đếm chẵn tức đang chọn cm thì vẽ độ dài theo mm, còn lẻ là chọn inch thì vẽ theo inch.
Vậy là xong class vẽ, nếu muốn vẽ gạch cả vào chiều ngang màn hình, bạn sẽ dùng l.x để vẽ.
Bây giờ sang class chính để hiển thị những gì vẽ ra, thêm các nút bấm vào.
Copy một loạt biến sau lên trên viewDidLoad
var bu: UIButton!
var bu2: UIButton!
var denn=0
var diem=0
var x=0.0
var ik=0.0
var gv=0
   var scale:CGFloat?
let c = UIScreen.mainScreen().bounds.size.height
let ruler = five()
Ta tạo ra đối tượng ruler để truy xuất class có hàm drawRect vẽ kia.
Copy xuống dưới viewDidLoad
scale = UIScreen.mainScreen().scale;
ifc<500{
x = 3.5
        }
elseifc<600&&c>500{
x = 4
        }
elseifc<700&&c>600{
x = 4.7
        }
elseifc<800&&c>700{
x = 5.5
        }
elseifc<1030&&c>800{
ifscale==1.0{
x = 7.9
            }
else{
x = 9.7
            }
        }
else{

x = 12.9
        }
ik=x


Ta dùng biến c là chiều cao màn hình và chỉ số scale để biết số inch. Các thiết bị khác nhau có chiều dài tương ứng số inch đường chéo khác nhau. Ta lấy được số inch để dùng. Bạn có thể cập nhật thêm nếu các đời iphone sau có thay đổi.
Copy tiếp xuống dưới dòng ik=x
bu = UIButton(frame: CGRect(x: -20, y: 50, width: 80, height: 40))
let a=NSMutableAttributedString(string: "Cm->Inch", attributes: [NSForegroundColorAttributeName: UIColor.blueColor(), NSFontAttributeName: UIFont(name: "Georgia", size: 16.0)!])
bu.setAttributedTitle(a, forState: .Normal)
bu2 = UIButton(frame: CGRect(x: 3, y: Int(c-34), width: 50, height: 42))
bu2.setTitle("Quit",forState: .Normal)
bu2.setTitleColor(UIColor.grayColor(), forState: .Normal)
Đây là ta vẽ cái nút chuyển cm->inch và nút Quit, tô màu chữ cho chúng.
Copy tiếp xuống dưới
ruler.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: c)
ruler.backgroundColor = UIColor.whiteColor()
ifgv%2==1{
diem=2
denn=1
ik=x/2.54
        }
else{
ik=x
        }
ruler.den=denn
ruler.x=ik

UIView.animateWithDuration(0.0, animations: {
self.bu.transform = CGAffineTransformMakeRotation(CGFloat(90) * CGFloat(M_PI)/180)
        })
view.addSubview(ruler)
view.addSubview(bu)
view.addSubview(bu2)
bu.addTarget(self, action: #selector(seven.cmin(_:)), forControlEvents: UIControlEvents.TouchUpInside)
bu2.addTarget(self, action: #selector(seven.bun(_:)), forControlEvents: UIControlEvents.TouchUpInside)


Ta chèn view kín màn hình, đổ nền trắng, truyền giá trị biến đếm khi người dùng kích nút bấm cm->inch và chỉ số kích thước theo inch vào class vẽ qua đối tượng ruler đã tạo.
Tiếp theo là quay cái nút cm->inch đi 90 độ cho nó dọc màn hình.
Cuối cùng nhét tất cả vào view và set hàm cho bút bấm.
Copy các hàm cần dùng xuống ngoài viewDidLoad.
func cmin(sender: UIButton){
denn=denn+1
ifdenn%2==1{
diem=2
ik=ik/2.54
        }
else{
diem=1
ik=ik*2.54
        }
ruler.den=denn
ruler.x=ik
ruler.setNeedsDisplay()
    }
func bun(sender: UIButton){
rite2("","danhsach.txt")
rite(String(denn),fileName: "danhsach.txt")
exit(0)
    }
Hàm cmin() của nút cm->inch là để điều chỉnh biến đếm xem là chọn cm hay inch thì hiển thị tương ứng.
Bây giờ có một vấn đề là ta muốn nếu khi thoát người dùng để là cm hay inch thì lúc mở ra nó vẫn phải y như thế.
Để biết ta dùng biến đếm, nếu nó lẻ tức đang là inch, chẵn là cm. Khi thoát ta xoá trắng file danhsach và ghi vào số denn của biến đếm.
Copy các hàm đọc ghi xuống trên ngoặc đóng cuối cùng.
func rite(content: String, fileName: String) {
let contentToAppend = content
let filePath = NSHomeDirectory() + "/Documents/" + fileName
//Check if file exists
iflet fileHandle = NSFileHandle(forWritingAtPath: filePath) {
//Append to file
            fileHandle.seekToEndOfFile()     fileHandle.writeData(contentToAppend.dataUsingEncoding(NSUTF8StringEncoding)!)
        }
else {
//Create new file
do {
try contentToAppend.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding)
            } catch {
print("Error creating \(filePath)")
            }
        }
    }
func rite2(content: String,_ fileName: String) {
let contentToAppend = content
let filePath = NSHomeDirectory() + "/Documents/" + fileName
//Check if file exists
iflet fileHandle = NSFileHandle(forWritingAtPath: filePath) {
//Append to file
//  fileHandle.seekToEndOfFile()
// fileHandle.writeData(contentToAppend.dataUsingEncoding(NSUTF8StringEncoding)!)
do {
try content.writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding)
            } catch {
print("Error creating \(filePath)")
            }
        }
else {
//Create new file
do {
try contentToAppend.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding)
            } catch {
print("Error creating \(filePath)")
            }
        }
    }
func doc()->Int{
let documentsPath2 = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] asNSString
let path2 = documentsPath2.stringByAppendingPathComponent("danhsach.txt")
var rea2 : String = ""
var t2="g"
var t3=10
do {
try rea2 = NSString(contentsOfFile: path2, encoding: NSUTF8StringEncoding) asString
var ar = rea2.componentsSeparatedByString("\n")
            t2=ar[0]
            t3=Int(t2)!
        }
catchlet error asNSError {
// print("ERROR : reading from file \(fileName) : \(error.localizedDescription)")
        }
return t3
    }
Copy dòng sau vào dưới dòng định vị các nút trong viewDidLoad
gv = doc()


Ta đọc file danhsach.txt để lấy số ghi vào biết chẵn lẻ mà set giá trị tương ứng cho biến đếm và đơn vị đo kích thước màn hình. Nếu lần đầu mở số đọc ra là 0 tức sẽ luôn là vẽ theo cm.
Chạy chương trình để có kết quả, nháy thử nút cm->inch để xem thay đổi.


Để hiểu cách làm ứng dụng này, bạn nên thực hành vẽ các đường thẳng, chữ trong class là subclass của UIView trước, sẽ thấy nó rất cơ bản. Code chưa dài lắm, chỉ hơn trăm dòng, đều là những thứ quen thuộc.

No comments:

Post a Comment