干货|swift,富文本编辑器

作者: StrongX | 来源:发表于2015-05-07 21:51 被阅读8187次

    这一篇我们将实现一个富文本编辑器,拥有功能:

    • 1、斜体、下划线混排。
    • 2、图文混排
    • 3、字体大小。
    • 4、选择自定义字体。
    • 5、制作长图片分享到微信朋友圈与微信好友。
    • 6、调用系统邮件发送文本。

    先来看一下大体的效果吧,还有一些效果将在后面演示:
    (写在最前面,这个Demo存在大量BUG,我只是通过他来演示一些功能,也许在后面我会做一个完整的APP,到时候也会再来写一遍,来说说一些BUG如何处理)。

    1.png

    富文本编辑器在以前需要使用CoreText来实现,但是不得不说这真的是一个不小的工程,但是在iOS7发布以后,apple发布了TextKit,通过TextKit我们能够轻松实现很多从前难以实现的功能。


    1、斜体、下划线混排、字体的增大以及减小
    在SB中拖入一个UITextView,此后的所有操作都是对这个TextView中的文字进行操作,先来看几行代码:
    <pre><code>
    var string = NSMutableAttributedString(attributedString: self.textview.attributedText)
    string.addAttribute(NSObliquenessAttributeName, value: 0.5, range: NSMakeRange(0,5))
    </code></pre>

    我们来解释一下这一段代码:
    首先,我们从TextView中获取了string,这个string是NSMutableAttributedString类型的,这个类型继承自String,但是呢,从名字上我们就可以看出来这个类型,我们可以给字符串添加不同的属性,我们回到代码。这段代码的第二句我们通过:

       func addAttribute(name: String, value: AnyObject, range: NSRange)
    

    这个函数给字符串添加了属性,这个API的第1、2个参数就是确定添加的属性类型,在这里我们添加的就是斜体这个属性,斜体这个属性的Value参数填写0~1之间的数值,在这个我们填写的是0.5.来看第二个参数,第二个参数是添加属性的范围,填写的是一个NSRange类型的值,应该不难理解。

    以上代码段的功能就是给textview的字符串的第0个字符开始,连续五个字符添加斜体的效果。

    是不是十分方便?~~~~~是的。

    以上内容只是为了演示UITextView中的attributedText属性,事实上我们不需要这么做(感谢刘大大,告诉我接下来这种做法)。
    在TextView中有一个属性叫做typingAttributes,xcode对这个属性的解释是这样的automatically resets when the selection changes,意思就是我们对这个属性进行设置可以改变接下来改变的文字。
    也许这样说,不能让人太好的理解,我们在按钮中添加以下代码
    <pre><code>
    @IBAction func Obliqueness(sender: AnyObject) {
    textview.typingAttributes[NSObliquenessAttributeName] = (textview.typingAttributes[NSObliquenessAttributeName] as? NSNumber) == 0 ? 0.5 : 0
    }

    </code></pre>

    当** NSObliquenessAttributeName**的值为0时,点击按钮将将之改变为1,为1时则相反。我们点一下试试,神奇的事情发生啦啦啦啦~~~接下来我们输入的文字都变成了斜体。再次点击则变回正常。

    以此类推,我们写出下划线、字体的增大以及减小的代码。
    <pre><code>
    /**
    字体减小

    :param: sender
    */
    @IBAction func fontincrease(sender: AnyObject) {
        self.fontSize -= 2
        self.textview.typingAttributes[NSFontAttributeName] = UIFont.systemFontOfSize((CGFloat)(self.fontSize))
    }
    /**
    字体增大
    
    :param: sender
    */
    @IBAction func fontdecase(sender: AnyObject) {
        self.fontSize += 2
        self.textview.typingAttributes[NSFontAttributeName] = UIFont.systemFontOfSize((CGFloat)(self.fontSize))
    
    }
    /**
    设置斜体
    
    :param: sender
    */
    @IBAction func Obliqueness(sender: AnyObject) {
        textview.typingAttributes[NSObliquenessAttributeName] = (textview.typingAttributes[NSObliquenessAttributeName] as? NSNumber) == 0 ? 0.5 : 0
    }
    /**
    设置下划线
    
    :param: sender
    */
    @IBAction func underline(sender: AnyObject) {
        self.textview.typingAttributes[NSUnderlineStyleAttributeName] =  (NSUnderlineStyle.StyleSingle.hashValue ) == 0 ? 1 : NSUnderlineStyle.StyleSingle.hashValue
    }
    /**
    

    </code></pre>

    实现方法都是类似的,十分方便的已经实现了很多功能,这放在以前是不可能的(其实我不知道以前实现究竟有多复杂——!)。


    2、插入图片。
    前面我们知道TextView存在一个属性叫做attributedText,插入图片需要做的就是在TextView的这个属性中添加图片,上代码。
    <pre><code>
    //1
    var string = NSMutableAttributedString(attributedString: self.textview.attributedText)
    //2
    var textAttachment = NSTextAttachment()
    textAttachment.image = img
    //3
    var textAttachmentString = NSAttributedString(attachment: textAttachment)
    var countString:Int = count(self.textview.text) as Int
    string.insertAttributedString(textAttachmentString, atIndex: countString)
    //4
    textview.attributedText = string
    </code></pre>

    • 1、获取当前的attributedString
    • 2、新建一个NSTextAttachment,设置他的图片属性
    • 3、将刚刚创建的NSTextAttachment,添加在原本的attributedString的最后面
    • 4、重定义** textview.attributedText **

    以上代码中出现一个变量img,这个变量就是从系统相册获取图片,代码如下:
    <pre><code>
    @IBAction func photeSelect(sender: AnyObject) {
    var sheet:UIActionSheet
    if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
    sheet = UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "取消", destructiveButtonTitle: nil, otherButtonTitles: "从相册选择", "拍照")
    }else{
    sheet = UIActionSheet(title:nil, delegate: self, cancelButtonTitle: "取消", destructiveButtonTitle: nil, otherButtonTitles: "从相册选择")
    }
    sheet.showInView(self.view)
    }
    func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
    var sourceType = UIImagePickerControllerSourceType.PhotoLibrary
    if(buttonIndex != 0){
    if(buttonIndex==1){ //相册
    sourceType = UIImagePickerControllerSourceType.PhotoLibrary
    }else{
    sourceType = UIImagePickerControllerSourceType.Camera
    }
    let imagePickerController:UIImagePickerController = UIImagePickerController()
    imagePickerController.delegate = self
    imagePickerController.allowsEditing = true//true为拍照、选择完进入图片编辑模式
    imagePickerController.sourceType = sourceType
    self.presentViewController(imagePickerController, animated: true, completion: {
    })
    }
    }
    </code></pre>

    首先是弹出一个alertView,让你进行选择,选择以后,将调用下面的方法,进入到相册进行选择图片。在这里你需要继承几个协议,不然在选择以后不会触发下面的方法:UIActionSheetDelegate,UIImagePickerControllerDelegate。同时进入相册你需要添加几个库:AssetsLibrary.framework和MobileCoreServices.framework。具体代码解释在这里就不行进解释,今天我们把重点放在富文本的实现上。当你选择图片以后,将促发以下方法,这也是前面添加的协议的功能。
    <pre><code>
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]){
    var string = NSMutableAttributedString(attributedString: self.textview.attributedText)
    var img = info[UIImagePickerControllerEditedImage] as! UIImage
    img = self.scaleImage(img)

        var textAttachment       = NSTextAttachment()
        textAttachment.image     = img
    
        var textAttachmentString = NSAttributedString(attachment: textAttachment)
        var countString:Int      = count(self.textview.text) as Int
        string.insertAttributedString(textAttachmentString, atIndex: countString)
    
        textview.attributedText  = string
        self.textview.becomeFirstResponder()
        picker.dismissViewControllerAnimated(true, completion: nil)
    }
    

    </code></pre>
    同时在这个函数中我们给文本插入图片,插入方法在前面已经说过了。

    当我们选择图片以后你会发现由于图片太大,所以在界面上只能显示一部分,那么我们就需要压缩图片,压缩方法如下:
    <pre><code>
    func scaleImage(image:UIImage)->UIImage{
    UIGraphicsBeginImageContext(CGSizeMake(self.view.bounds.size.width, image.size.height(self.view.bounds.size.width/image.size.width)))
    image.drawInRect(CGRectMake(0, 0, self.view.bounds.size.width, image.size.height
    (self.view.bounds.size.width/image.size.width)))
    var scaledimage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return scaled image
    }
    </code></pre>
    这个方法将图片的宽度设置为屏幕的宽度,高度按比例缩放。

    好的~~~~~我们终于实现了插入图片,其实在这里有一个很大的问题,就是下面这句话:

    <pre><code>
    textview.attributedText = string
    </code></pre>

    我们每次插入图片都将TextView的内容全部改变了一次,这样的做法,先不提在大量文字的情况下可能造成的卡顿问题,同时造成了编排问题,有同学可能已经发现了,当我们用这样的方法插入图片以后,当我们再次改变属性的时候,视图将会瞬间调到最上面,原因就是这句话。至于如何解决~~~~还没想到——!


    3、选择字体
    改变字体的方法其实和我们设置斜体,下划线等是一样的,方法如下:
    <pre><code>
    func lovefont(sender:AnyObject){
    self.textview.typingAttributes[NSFontAttributeName] = UIFont(name: "1-", size: (CGFloat)(self.fontSize))
    }
    </code></pre>
    那么我们现在的问题就是自定义字体了,毕竟xcode的字体大多不支持中文,同时中文显示的时候不那么优雅,我们要说的就是自定义字体。

    加载自定义字体,并不是太过复杂,我在简书看到这篇文章描述加载自定义字体就感觉写的很好,http://www.jianshu.com/p/d728570bdf7b 小伙伴们有兴趣的话自行跳转过去看吧,这里就不重复介绍了。

    4、制作长图片分享到微信朋友圈与微信好友。

    那么我们需要做的第一步就是制作长图片,代码如下:
    <pre><code>
    func madelongPicture() -> UIImage {

        var image : UIImage!
        UIGraphicsBeginImageContext(self.textview.contentSize)
        var savedContentOffset      = self.textview.contentOffset
        var savedFrame              = self.textview.frame
        self.textview.contentOffset = CGPointZero
        self.textview.frame         = CGRectMake(0, 0, self.textview.contentSize.width, self.textview.contentSize.height)
        self.textview.layer.renderInContext(UIGraphicsGetCurrentContext())
        image                       = UIGraphicsGetImageFromCurrentImageContext()
        self.textview.contentOffset = savedContentOffset
        self.textview.frame         = savedFrame
        UIGraphicsEndPDFContext()
        return image
    }
    

    </code></pre>

    TextView继承自ScorllView,所以我们只需要给TextView进行截图,就可以制作一张长图片了,方法如上。然后我们需要做的就是调用微信给我们的API,将图片分享到朋友圈。

    由于分享的话要申请AppId,所以在这里没有实现这个功能,不过实现的方法并不复杂。

    微信分享的文档地址:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN

    其实里头已经讲的很清楚了,有不明白的亲可以留言哦~


    5、调用系统邮件发送文本

    调用系统邮件,你可以将刚刚写好的文章或者啥的发送给好朋友,或者交给老师检查~~~~~~~首先我们还是得制作长图片,制作方法上面已经讲过了,就不重复累赘了,这里讲一下如何调用系统邮件。

    首先你得继承一个协议:MFMailComposeViewControllerDelegate

    然后代码如下:
    <pre><code>
    @IBAction func email(sender: AnyObject) {
    UIApplication.sharedApplication().keyWindow?.endEditing(true)
    var configuredMailComposeViewController = MailComposeViewController()
    if canSendMail() {
    presentViewController(configuredMailComposeViewController, animated: true, completion: nil)
    } else {
    showSendMailErrorAlert()
    }
    }

    func MailComposeViewController() -> MFMailComposeViewController {
        let mailComposerVC                 = MFMailComposeViewController()
        mailComposerVC.mailComposeDelegate = self
    
        mailComposerVC.setToRecipients(nil)
        mailComposerVC.setSubject(nil)
        mailComposerVC.setMessageBody(self.textview.text, isHTML: false)
        var addPic                         = self.madelongPicture()
        var imageData                      = UIImagePNGRepresentation(addPic)
        mailComposerVC.addAttachmentData(imageData, mimeType: "", fileName: "longPicture.png")
        return mailComposerVC
    }
    func canSendMail() -> Bool {
        return MFMailComposeViewController.canSendMail()
    }
    func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
        controller.dismissViewControllerAnimated(true, completion: nil)
    }
    func showSendMailErrorAlert() {
        let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail.  Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
        sendMailErrorAlert.show()
    }
    

    </code></pre>

    代码就在上面了,自己感受一下的,解释不动了。
    效果如下:

    1.png

    接下来也许会造个轮子,同时解决所有的BUG(至少我能发现的)。

    代码已上传Github:https://github.com/superxlx/textDemo

    亲们,自己下载代码感受一下,然后喜欢的换请点个喜欢同时关注一下我。

    相关文章

      网友评论

      • 慧煎蛋:git代码要更新下了, swift2, xcode9都不支持了
      • 巴图鲁:不错
      • 32b87918d241:有人知道如何处理NSAttributedString前后空格留白的问题吗?总是把空格存起来,怎么把它trim掉?
      • 沉默的眼睛Q_Q:有安卓版本的介绍吗
      • 在草地上奔跑的壮汉:少年郎,你说的那个self.textView.attributedText = attrString; 会刷新TextView的问题,俺解决了,只需要写个子类,重写到scrollRectToVisible方法就可以了http://stackoverflow.com/questions/16716525/replace-uitextviews-text-with-attributed-string :sunglasses:
      • 刘小壮:我正在查找UILabel文字斜体的实现方式,看到你的博客,解决了我的问题,谢谢!
      • lfb_CD:@StrongX 我把nsattributeedString转换为string存了,但是取出来,我就不知道该怎么转换回去了,又不能直接用NSAttributedString转换…
        brzhang:@lfb_CD NSAttributedString(string)难道不行?
      • StrongX:@清水流 直接储存attributedText,重新加载的时候初始化attributedText,不过我没试过啊—。—
      • lfb_CD:@StrongX 谢谢回复,但是还是不太理解。在textView里面,是有不同字体数据的啊?有的没有斜体,有的是斜体的,我不知道用什么方法储存啊
      • StrongX:@清水流 用coredata进行储存比较容易,比如下划线等数据类型都是NSNumber,有的是NSString类型,至于CoreData如何进行储存,请自行阅读我写的CoreData系列博文。
      • lfb_CD:非常感谢!一直在找怎么改变输入时的字体还有字体大小,终于找到了!
        我有个问题:这些字体数据怎么保存啊?我想保存到数据库,下次打开的时候直接是之前设置好的格式 :flushed:
      • StrongX:@MarcyLi 哪位老刘😳
      • 李小争:有老刘风格。。。

      本文标题:干货|swift,富文本编辑器

      本文链接:https://www.haomeiwen.com/subject/ldzifttx.html