iOS Question MeasureMultilineTextHeight in IOS

Alessandra Pellegri

Active Member
Licensed User
I am porting a code from B4A and I should measure the height of a multiline text.
I cannot use label.SizeToFit because I have to fix the width and determine the height.

How could I do ?

Thank you
 

Alessandra Pellegri

Active Member
Licensed User
I am so sorry but I don't understand your suggestion. I am porting the baloon library for B4A in B4i. It extensively uses MeasureMultilineTextHeight to determine the right height once the width is defined. Moreover I modified this library to allow the user to resize the width of the ballons. SizeToFit doesn't appear to me able to lock the width.

In CustomListView I find this code:
B4X:
  lbl.Text = Text
   lbl.Multiline = True
   lbl.Font = DefaultTextFont
   lbl.TextColor = DefaultTextColor
   pnl.Color = DefaultTextBackgroundColor
   lbl.SizeToFit
   lbl.Height = Max(50dip, lbl.Height)
But it seems a different case because here the width is not predetermined.

Could you explain me please ?

Thank you very much
 
Upvote 0

Alessandra Pellegri

Active Member
Licensed User
I tried to do it but the result is strange: the label becomes very high, 10 times more than what is necessary.

Here my code:
B4X:
'Class module
Sub Class_Globals
Dim su As StringUtils   
Public iv As ImageView
Public p As Panel
Private  lb1, lb2 As Label
Private img As Bitmap
Private Bwidth, Height As Int
Public TYPE1 As String  = "bubble1.png"
Public TYPE2 As String  = "bubble2.png"
Public TYPE3 As String  = "bubble3.png"
Public ALIGNMENT_LEFT = 0
Public ALIGNMENT_CENTER = 1
Public ALIGNMENT_RIGHT = 2
Private nomeEvento As String=""
Private CF As Object
Dim SF As Float
End Sub

'Initializes the object.
#if b4a
Public Sub Initialize(Act As Activity,aCF As Object, aNomeEvento As String )
p.Initialize("BaloonEvent")
iv.Initialize("")
lb1.Initialize("")
lb2.Initialize("")
SF = Max(Act.Width,Act.Height)/800
nomeEvento=aNomeEvento
CF=aCF

p.AddView(lb1,0,0,1,1)
p.AddView(lb2,0,0,1,1)
p.addview(iv,0,0,1,1)
End Sub
#end if

#if b4i
Public Sub Initialize(Act As Panel,aCF As Object, aNomeEvento As String )
   p.Initialize("BaloonEvent")
   iv.Initialize("")
   lb1.Initialize("")
   lb2.Initialize("")
   SF = Max(Act.Width,Act.Height)/800
   nomeEvento=aNomeEvento
   CF=aCF

   p.AddView(lb1,0,0,1,1)
   p.AddView(lb2,0,0,1,1)
   p.addview(iv,0,0,1,1)
End Sub
#end if

Private Sub BaloonEvent_Click
   If nomeEvento<>"" Then
     If SubExists(CF,nomeEvento&"_Click",0) Then
       CallSub(CF,nomeEvento&"_Click")
     End If
   End If   
End Sub

Private Sub BaloonEvent_LongClick
   If nomeEvento<>"" Then
     If SubExists(CF,nomeEvento&"_LongClick",0) Then
       CallSub(CF,nomeEvento&"_LongClick")
     End If
   End If
End Sub

'Sets the parameters of the labels appearance.
'text1 is the text of the first label, text2 is for the second label.
'Backcolor is the color of the baloon.
'Returns the height of the object ( to be used by addview)
Public Sub Appearance(BaloonType As String,textsize1 As Int, textsize2 As Int, textcolor1 As Int, textcolor2 As Int, text1 As String, text2 As String, Backcolor As Int,TextGravity As Int, Width As Int) As Int

lb1.text = text1
lb2.text = text2
#IF b4a
lb1.TextSize = textsize1
lb2.TextSize = textsize2
lb1.Gravity = TextGravity
lb2.Gravity = TextGravity
#end if
#IF b4i
lb1.Font=Font.CreateNew(textsize1)
lb2.Font=Font.CreateNew(textsize2)
lb1.TextAlignment=TextGravity
lb2.TextAlignment=TextGravity
lb1.Multiline=True
lb1.SizeToFit   
lb2.Multiline=True
lb2.SizeToFit
#end if
lb1.textcolor = textcolor1
lb2.textcolor = textcolor2
p.Color = Backcolor
#IF b4a
iv.Gravity = iv. Gravity.FILL
img = LoadBitmap(File.DirAssets,BaloonType)
iv.SetBackgroundImage(img)
#end if
#IF b4i
img = LoadBitmap(File.DirAssets,BaloonType)
iv.Bitmap=img
#end if


lb1.Width=Width*0.85

lb2.Width=lb1.Width



lb1.Color = Colors.Transparent
lb2.Color = Colors.Transparent


#IF b4a
lb1.Height =  su.MeasureMultilineTextHeight(lb1,lb1.text)
lb2.Height =  su.MeasureMultilineTextHeight(lb2,lb2.text)
If lb1.Text<>"" Then
   Height =  lb1.Height+lb2.Height+lb1.TextSize+(lb1.Height+lb2.Height+lb1.TextSize)*0.65
Else
   Height =  lb2.Height+(lb2.Height)*0.65
End If
#end if
#IF b4i
If lb1.Text<>"" Then
   Height =  lb1.Height+lb2.Height+lb1.Font.Size+(lb1.Height+lb2.Height+lb1.Font.Size)*0.65
Else
   Height =  lb2.Height+(lb2.Height)*0.65
End If
#end if

Height=Max(Height,80dip)

lb1.Left=Width*0.1
lb2.Left=lb1.Left
lb1.Top=Height/10
#if b4a
If lb1.Text<>"" Then
   lb2.Top=lb1.Top+lb1.Height+lb1.TextSize
Else
   lb2.Top=lb1.Top
End If
#end if
#if b4i
If lb1.Text<>"" Then
   lb2.Top=lb1.Top+lb1.Height+lb1.Font.Size
Else
   lb2.Top=lb1.Top
End If
#end if

p.Height=Height+3
iv.Left=-1
iv.Top=-1
iv.Width=Width+3
iv.Height=Height+4
iv.SendToBack
Bwidth = Width
Return p.Top+p.Height
End Sub

Public Sub ChangeWidth(Width As Int)
   lb1.Width=Width*0.85
   lb2.Width=lb1.Width
   
#if b4a
   lb1.Height =  su.MeasureMultilineTextHeight(lb1,lb1.text)
   lb2.Height =  su.MeasureMultilineTextHeight(lb2,lb2.text)
   If lb1.Text<>"" Then
     Height =  lb1.Height+lb2.Height+lb1.TextSize+(lb1.Height+lb2.Height+lb1.TextSize)*0.65
   Else
     Height =  lb2.Height+(lb2.Height)*0.65
   End If
#end if

#if b4i
   lb1.Multiline=True
   lb1.SizeToFit   
   lb2.Multiline=True
   lb2.SizeToFit
   If lb1.Text<>"" Then
     Height =  lb1.Height+lb2.Height+lb1.Font.Size+(lb1.Height+lb2.Height+lb1.Font.Size)*0.65
   Else
     Height =  lb2.Height+(lb2.Height)*0.65
   End If
#end if
   Height=Max(Height,80dip)

   lb1.Left=Width*0.1
   lb2.Left=lb1.Left
   lb1.Top=Height/10
#if b4a   
   If lb1.Text<>"" Then
     lb2.Top=lb1.Top+lb1.Height+lb1.TextSize
   Else
     lb2.Top=lb1.Top
   End If
#end if
#if b4i   
   If lb1.Text<>"" Then
     lb2.Top=lb1.Top+lb1.Height+lb1.Font.Size
   Else
     lb2.Top=lb1.Top
   End If
#end if
   p.Height=Height+3
   iv.Left=-1
   iv.Top=-1
   iv.Width=Width+3
   iv.Height=Height+4
   iv.SendToBack
   Bwidth = Width
   p.Width=Width
End Sub

'Modify the text of the two labels.
'The height of the baloon is re-calculated.
Sub setText(text1 As String, text2 As String)
lb1.text = text1
lb2.text = text2
lb1.Top=20dip
lb1.left=20dip
#if b4a
   lb1.Height =  su.MeasureMultilineTextHeight(lb1,lb1.text)
   lb2.SetLayout(20dip,20dip+lb1.Height+lb1.Top,p.Width-40dip,4dip*SF)
   lb2.Height =  su.MeasureMultilineTextHeight(lb2,lb2.text)
#end if   
#if b4i
   lb1.Multiline=True
   lb1.SizeToFit   
   lb2.Left=20dip
   lb2.Top=20dip+lb1.Height+lb1.Top
   lb2.Width=p.Width-40dip
   lb2.Height=4dip*SF
   lb2.Multiline=True
   lb2.SizeToFit   
#end if   
Height =  lb2.Top+lb2.Height+(lb2.Top+lb2.Height)*0.3
iv.Height = Height +lb1.top
p.Height = Height + lb1.top
End Sub

'Returns the panel which holds all the views.
Sub AsView As View
Return p
End Sub

'Flips the baloon so the tip can be on either side , top or bottom.
Sub Flip(Vertical As Boolean, Horizontal As Boolean)
Dim cnvs As Canvas
Dim src,dst As Rect
src.Initialize(0,0,img.Width,img.Height)
dst.Initialize(0,0,iv.Width,iv.Height)

cnvs.Initialize(iv)
cnvs.DrawRect(dst,Colors.Transparent,True,1)
'cnvs.DrawBitmapFlipped(img,src,dst,Vertical,Horizontal)
'iv.Invalidate
End Sub

'If the background of the parent view (activity or panel) is not black, it should be changed using this method.
'Use this method after Flip.
Sub MatchBackground(clr As Int)
Dim cnvs As Canvas
Dim dst As Rect
dst.Initialize(0,0,iv.Width,iv.Height)
cnvs.Initialize(iv)
For i = 1 To iv.Width-1
   For j = 1 To iv.Height-1
     'If cnvs.Bitmap.GetPixel(i,j) <> Colors.Transparent Then cnvs.DrawPoint(i,j, clr)
   Next
Next
'iv.Invalidate
End Sub

Main:
B4X:
'Code module
#Region  Project Attributes
   #ApplicationLabel: B4i Example
   #Version: 1.0.0
   'Orientation possible values: Portrait, LandscapeLeft, LandscapeRight and PortraitUpsideDown
   #iPhoneOrientations: Portrait, LandscapeLeft, LandscapeRight
   #iPadOrientations: Portrait, LandscapeLeft, LandscapeRight, PortraitUpsideDown
#End Region

Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'Public variables can be accessed from all modules.
   Public App As Application
   Public NavControl As NavigationController
   Private Page1 As Page
   Private BlnChatQ As Baloon

   Private Panel1 As Panel
   Private Button1 As Button
End Sub

Private Sub Application_Start (Nav As NavigationController)
   NavControl = Nav
   Page1.Initialize("Page1")
   Page1.RootPanel.LoadLayout("Prima")
   Page1.Title = "Page 1"
   Page1.RootPanel.Color = Colors.White
   NavControl.ShowPage(Page1)
End Sub

Private Sub Page1_Resize(Width As Int, Height As Int)
     BlnChatQ.Initialize(Page1.RootPanel,Me,"ChatBaloonQ")
     Page1.RootPanel.AddView(BlnChatQ.AsView,0,20dip,400dip,10)
   Dim H As Int
   H=BlnChatQ.appearance(BlnChatQ.TYPE1,18,18,Colors.Red,Colors.Black,"","wwwwwwwwfghdstfrtrsgtrgrtsgvtwww",Colors.Transparent, BlnChatQ.ALIGNMENT_LEFT ,200dip)     
End Sub

Private Sub Application_Background
   
End Sub

if I put:

lb1.Color=Colors.Yellow
lb2.Color=Colors.Red

after

lb1.Color=Colors.Transparent
lb2.Color=Colors.Transparent

I can easily see that the label is to big.
 
Upvote 0

Alessandra Pellegri

Active Member
Licensed User
Your example works, instead I don't know why SizeToFit creates a label too much big (2 or 3 times bigger than what is necessary).

Anyway I solved the problem doing:
B4X:
  Dim tmpString As String ="A"
   lb1.Height=(Ceil(lb1.Text.MeasureWidth(lb1.Font)/lb1.Width)+1)*tmpString.MeasureHeight(lb1.Font)

Now it works good.

Thank you very much
 
Upvote 0

328230795

New Member
Licensed User
B4X:
lb3.Height = getTextHeight(lb3.Text,lb3.Font,lb3.Width)
Sub getTextHeight(content As String,fo As Font,LbWidth As Float) As Float
  
    Dim tmpString As String = "大"
    Dim str() As String = Regex.Split(Chr(10),content)
    Dim height As Float
    Dim number As Int
    Dim fontHeight As Float = tmpString.MeasureHeight(fo)
    For Each s As String In str
        number = s.MeasureWidth(fo)/LbWidth + 1
        height = height + number*fontHeight
    Next
    Return height + fontHeight
End Sub
 
Upvote 0
Top